[automerger skipped] Merge qt-r1-dev-plus-aosp-without-vendor (5817612) into stage-aosp-master am: c09c875c6a -s ours am: 2d13e2124b -s ours
am: cbfb9593be -s ours
am skip reason: change_id I473cbf8f4f53a00f4c7dafce18a5ad23a1614332 with SHA1 b0c5908615 is in history
Change-Id: I9d725eb2daaba5205a13943df4c75db11b31a3c7
diff --git a/Android.bp b/Android.bp
index 4268636..83c232b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,8 +14,8 @@
// limitations under the License.
//
-version_name = "1.20-asop"
-version_code = "417000328"
+version_name = "1.23-asop"
+version_code = "417000410"
android_app {
name: "LiveTv",
@@ -44,16 +44,13 @@
static_libs: [
"android-support-annotations",
"android-support-compat",
- "android-support-core-ui",
- "androidx.tvprovider_tvprovider",
- "android-support-v4",
- "android-support-v7-appcompat",
- "android-support-v7-palette",
- "android-support-v7-preference",
"android-support-v7-recyclerview",
- "android-support-v14-preference",
- "android-support-v17-leanback",
- "android-support-v17-preference-leanback",
+ "androidx.legacy_legacy-support-core-ui",
+ "androidx.leanback_leanback",
+ "androidx.leanback_leanback-preference",
+ "androidx.palette_palette",
+ "androidx.preference_preference",
+ "androidx.tvprovider_tvprovider",
"jsr330",
"live-channels-partner-support",
"live-tv-tuner-proto",
@@ -62,6 +59,7 @@
"tv-auto-factory-jar",
"tv-common",
"tv-error-prone-annotations-jar",
+ "tv-javax-annotations-jar",
"tv-lib-dagger",
"tv-lib-exoplayer",
"tv-lib-exoplayer-v2-core",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a398823..7110160 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,12 +16,12 @@
-->
<!-- This manifest is for LiveTv -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.tv" >
<uses-sdk
android:minSdkVersion="23"
- android:targetSdkVersion="27" />
+ android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -79,8 +79,7 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.TV"
- tools:replace="android:appComponentFactory">
- >
+ tools:replace="android:appComponentFactory" >
<!-- providers are listed here to keep them separate from the internal versions -->
<provider
@@ -254,12 +253,16 @@
android:name="com.android.tv.recommendation.ChannelPreviewUpdater$ChannelPreviewUpdateService"
android:permission="android.permission.BIND_JOB_SERVICE" />
- <receiver android:name="com.android.tv.receiver.BootCompletedReceiver" >
+ <receiver
+ android:name="com.android.tv.receiver.BootCompletedReceiver"
+ android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
- <receiver android:name="com.android.tv.receiver.PackageIntentsReceiver" >
+ <receiver
+ android:name="com.android.tv.receiver.PackageIntentsReceiver"
+ android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<!-- PACKAGE_CHANGED for package enabled/disabled notification -->
@@ -290,11 +293,13 @@
android:name="com.android.tv.dvr.recorder.DvrRecordingService"
android:label="@string/dvr_service_name" />
- <receiver android:name="com.android.tv.dvr.recorder.DvrStartRecordingReceiver" />
+ <receiver
+ android:name="com.android.tv.dvr.recorder.DvrStartRecordingReceiver"
+ android:exported="false" />
<service
android:name="com.android.tv.data.epg.EpgFetchService"
android:permission="android.permission.BIND_JOB_SERVICE" />
</application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/README.md b/README.md
index 63c1f44..0659bbd 100644
--- a/README.md
+++ b/README.md
@@ -2,18 +2,6 @@
__Live TV__ is the Open Source reference application for watching TV on Android TVs.
-## Source
-
-The source of truth is an internal google repository (aka google3) at
-cs/third_party/java_src/android_app/live_channels
-
-Changes are made in the google3 repository and automatically pushed here.
-
-The following files are only in the android repository and must be changed there.
-
-* *.mk
-* \*\*/lib/\*.\*
-
## AOSP instructions
To install LiveTv
diff --git a/assets/rating_sources.html b/assets/rating_sources.html
index 50da7cc..ff4a005 100644
--- a/assets/rating_sources.html
+++ b/assets/rating_sources.html
@@ -89,4 +89,22 @@
<pre>
Source: http://www.mpaa.org/film-ratings/
</pre>
+<ul>
+ <li>TV content rating system strings for DTMB</li>
+</ul>
+<pre>
+ Source: http://www.gb688.cn/bzgk/gb/newGbInfo?hcno=59E83CA701AEB4248E115BC043688FEC
+</pre>
+<ul>
+ <li>Implementations details of TV content rating system strings for New Zealand</li>
+</ul>
+<pre>
+ Source: https://bsa.govt.nz/images/03_BSA_FREE-TO-AIR-TV_CLASSIFICATIONS_DRAFT.pdf
+</pre>
+<ul>
+ <li>TV content rating system strings for Thailand</li>
+</ul>
+<pre>
+ Source: https://broadcast.nbtc.go.th/law/dwl.php?id=NjAwODAwMDAwMDAx&file=ZGF0YS9kb2N1bWVudC9sYXcvZG9jL3RoLzYwMDgwMDAwMDAwMS5wZGY=
+</pre>
</body></html>
diff --git a/build.gradle b/build.gradle
index 23e3dbd..10cddcc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,37 +18,28 @@
/*
* Experimental gradle configuration. This file may not be up to date.
*/
+apply plugin: 'com.android.application'
buildscript {
repositories {
- mavenCentral()
google()
+ jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
+ classpath 'com.android.tools.build:gradle:3.4.2'
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
}
}
-apply plugin: 'com.android.application'
+
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
- dexOptions {
- preDexLibraries = false
- additionalParameters=['--core-library']
- javaMaxHeapSize "6g"
+
+ compileOptions() {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
- android {
- defaultConfig {
- resConfigs "en"
- }
- }
- defaultConfig {
- minSdkVersion 23
- targetSdkVersion 28
- versionCode 1
- versionName "1.0"
- }
+
buildTypes {
debug {
minifyEnabled false
@@ -57,10 +48,15 @@
minifyEnabled true
}
}
- compileOptions() {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+
+ defaultConfig {
+ minSdkVersion 23
+ resConfigs "en"
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
}
+
sourceSets {
main {
res.srcDirs = ['res', 'material_res']
@@ -70,30 +66,33 @@
}
}
-repositories {
- mavenCentral()
- jcenter()
- google()
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
}
dependencies {
- implementation 'androidx.appcompat:appcompat:1.0.2'
- implementation 'androidx.palette:palette:1.0.0'
- implementation 'androidx.leanback:leanback:1.0.0'
- implementation "androidx.tvprovider:tvprovider:1.0.0"
- implementation "androidx.recyclerview:recyclerview:1.0.0"
- implementation "androidx.recyclerview:recyclerview-selection:1.0.0"
- implementation "androidx.palette:palette:1.0.0"
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.core:core:1.0.2'
+ implementation 'androidx.palette:palette:1.0.0'
+ implementation 'androidx.leanback:leanback:1.1.0-alpha02'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
+ implementation 'androidx.tvprovider:tvprovider:1.0.0'
- implementation 'com.google.dagger:dagger:2.18'
- implementation 'com.google.dagger:dagger-android:2.18'
- annotationProcessor 'com.google.dagger:dagger-compiler:2.18'
- annotationProcessor 'com.google.dagger:dagger-android-processor:2.18'
+ annotationProcessor 'com.google.auto.factory:auto-factory:1.0-beta6'
+ implementation 'com.google.auto.factory:auto-factory:1.0-beta6'
+ annotationProcessor 'com.google.auto.value:auto-value:1.5.3'
+ implementation 'com.google.auto.value:auto-value:1.5.3'
+ implementation 'com.google.dagger:dagger:2.23'
+ implementation 'com.google.dagger:dagger-android:2.23'
+ annotationProcessor 'com.google.dagger:dagger-android-processor:2.23'
+ annotationProcessor 'com.google.dagger:dagger-compiler:2.23'
+ implementation 'com.google.guava:guava:28.0-jre'
- /*Not building with latest one (1.6.3)*/
- annotationProcessor 'com.google.auto.value:auto-value:1.5.4'
- implementation 'com.google.auto.value:auto-value:1.5.4'
- implementation 'javax.inject:javax.inject:1'
- implementation 'com.google.guava:guava:26.0-android'
- implementation project(':common')
-}
\ No newline at end of file
+ implementation 'javax.inject:javax.inject:1'
+
+ implementation project(':common')
+}
diff --git a/common/Android.bp b/common/Android.bp
index 63759d4..bb709cf 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -28,22 +28,27 @@
resource_dirs: ["res"],
libs: [
+ "android-support-annotations",
"tv-auto-value-jar",
"tv-auto-factory-jar",
- "android-support-annotations",
"tv-error-prone-annotations-jar",
- "tv-guava-android-jar",
- "jsr330",
- "tv-lib-dagger",
- "tv-lib-exoplayer",
- "tv-lib-exoplayer-v2-core",
- "android-support-compat",
- "android-support-core-ui",
- "android-support-v7-recyclerview",
- "android-support-v17-leanback",
+ "tv-javax-annotations-jar",
+
],
- static_libs: ["tv-lib-dagger-android"],
+ static_libs: [
+ "androidx.legacy_legacy-support-core-ui",
+ "androidx.appcompat_appcompat",
+ "androidx.preference_preference",
+ "androidx.leanback_leanback",
+ "androidx.tvprovider_tvprovider",
+ "tv-guava-android-jar",
+ "jsr330",
+ "tv-lib-dagger",
+ "tv-lib-exoplayer",
+ "tv-lib-exoplayer-v2-core",
+ "tv-lib-dagger-android",
+ ],
plugins: [
"tv-auto-value",
diff --git a/common/AndroidManifest.xml b/common/AndroidManifest.xml
index 7002d5f..eb7de57 100644
--- a/common/AndroidManifest.xml
+++ b/common/AndroidManifest.xml
@@ -17,6 +17,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.common"
android:versionCode="1">
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application />
</manifest>
diff --git a/common/build.gradle b/common/build.gradle
index f371475..b7bc886 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -21,37 +21,24 @@
apply plugin: 'com.android.library'
apply plugin: 'com.google.protobuf'
-buildscript {
- repositories {
- mavenCentral()
- google()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
- }
-}
+
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
- dexOptions {
- preDexLibraries = false
- additionalParameters = ['--core-library']
- javaMaxHeapSize "6g"
- }
- android {
- defaultConfig {
- resConfigs "en"
- }
+ compileOptions() {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 23
+ resConfigs "en"
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
+
buildTypes {
debug {
minifyEnabled false
@@ -66,10 +53,6 @@
buildConfigField "boolean", "NO_JNI_TEST", "false"
}
}
- compileOptions() {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
sourceSets {
main {
@@ -79,29 +62,32 @@
proto {
srcDir 'src/com/android/tv/common/compat/internal'
}
+ proto {
+ srcDir 'src/com/android/tv/common/flags/proto'
+ }
}
}
}
-repositories {
- mavenCentral()
- google()
-}
-
dependencies {
- implementation 'androidx.appcompat:appcompat:1.0.2'
- implementation 'androidx.palette:palette:1.0.0'
- implementation 'androidx.leanback:leanback:1.0.0'
- implementation "androidx.tvprovider:tvprovider:1.0.0"
- implementation "androidx.recyclerview:recyclerview:1.0.0"
- implementation "androidx.recyclerview:recyclerview-selection:1.0.0"
- implementation "androidx.palette:palette:1.0.0"
- implementation 'com.google.guava:guava:26.0-android'
- implementation 'com.google.protobuf:protobuf-java:3.0.0'
- implementation 'com.google.dagger:dagger:2.18'
- implementation 'com.google.dagger:dagger-android:2.18'
- annotationProcessor 'com.google.dagger:dagger-compiler:2.18'
- annotationProcessor 'com.google.dagger:dagger-android-processor:2.18'
+ implementation 'androidx.annotation:annotation:1.1.0'
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.leanback:leanback:1.1.0-alpha02'
+ implementation 'androidx.palette:palette:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
+ implementation 'androidx.tvprovider:tvprovider:1.0.0'
+
+ implementation 'com.google.android.exoplayer:exoplayer:r1.5.16'
+ implementation 'com.google.android.exoplayer:exoplayer-core:2.10.1'
+ annotationProcessor 'com.google.auto.value:auto-value:1.5.3'
+ implementation 'com.google.auto.value:auto-value:1.5.3'
+ implementation 'com.google.dagger:dagger:2.23'
+ implementation 'com.google.dagger:dagger-android:2.23'
+ annotationProcessor 'com.google.dagger:dagger-android-processor:2.23'
+ annotationProcessor 'com.google.dagger:dagger-compiler:2.23'
+ implementation 'com.google.guava:guava:28.0-jre'
+ implementation 'com.google.protobuf:protobuf-java:3.0.0'
}
protobuf {
// Configure the protoc executable
diff --git a/common/src/com/android/tv/common/BaseApplication.java b/common/src/com/android/tv/common/BaseApplication.java
index 45c3256..1a42120 100644
--- a/common/src/com/android/tv/common/BaseApplication.java
+++ b/common/src/com/android/tv/common/BaseApplication.java
@@ -21,17 +21,22 @@
import android.os.Build;
import android.os.StrictMode;
import android.support.annotation.VisibleForTesting;
+
+import com.android.tv.common.dev.DeveloperPreferences;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.util.Clock;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.common.util.Debug;
-import com.android.tv.common.util.SystemProperties;
+
+import dagger.Lazy;
import dagger.android.DaggerApplication;
-/** The base application class for Live TV applications. */
+import javax.inject.Inject;
+
+/** The base application class for TV applications. */
public abstract class BaseApplication extends DaggerApplication implements BaseSingletons {
- private RecordingStorageStatusManager mRecordingStorageStatusManager;
+ @Inject Lazy<RecordingStorageStatusManager> mRecordingStorageStatusManager;
/**
* An instance of {@link BaseSingletons}. Note that this can be set directly only for the test
@@ -65,7 +70,7 @@
// Only set StrictMode for ENG builds because the build server only produces userdebug
// builds.
- if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
+ if (BuildConfig.ENG && DeveloperPreferences.ALLOW_STRICT_MODE.get(this)) {
StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog();
// TODO(b/69565157): Turn penaltyDeath on for VMPolicy when tests are fixed.
@@ -99,9 +104,6 @@
@Override
@TargetApi(Build.VERSION_CODES.N)
public RecordingStorageStatusManager getRecordingStorageStatusManager() {
- if (mRecordingStorageStatusManager == null) {
- mRecordingStorageStatusManager = new RecordingStorageStatusManager(this);
- }
- return mRecordingStorageStatusManager;
+ return mRecordingStorageStatusManager.get();
}
}
diff --git a/common/src/com/android/tv/common/BaseSingletons.java b/common/src/com/android/tv/common/BaseSingletons.java
index 1053061..8a3820d 100644
--- a/common/src/com/android/tv/common/BaseSingletons.java
+++ b/common/src/com/android/tv/common/BaseSingletons.java
@@ -18,15 +18,28 @@
import com.android.tv.common.buildtype.HasBuildType;
import com.android.tv.common.flags.has.HasCloudEpgFlags;
-import com.android.tv.common.flags.has.HasConcurrentDvrPlaybackFlags;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.util.Clock;
/** Injection point for the base app */
-public interface BaseSingletons
- extends HasCloudEpgFlags, HasBuildType, HasConcurrentDvrPlaybackFlags {
+public interface BaseSingletons extends HasCloudEpgFlags, HasBuildType {
+ /*
+ * Do not add any new methods here.
+ *
+ * To move a getter to Injection.
+ * 1. Make a type injectable @Singleton.
+ * 2. Mark the getter here as deprecated.
+ * 3. Lazily inject the object in TvApplication.
+ * 4. Move easy usages of getters to injection instead.
+ * 5. Delete the method when all usages are migrated.
+ */
+
+ /* @deprecated use injection instead. */
+ @Deprecated
Clock getClock();
+ /* @deprecated use injection instead. */
+ @Deprecated
RecordingStorageStatusManager getRecordingStorageStatusManager();
}
diff --git a/common/src/com/android/tv/common/buildtype/BuildTypeFactory.java b/common/src/com/android/tv/common/buildtype/BuildTypeFactory.java
new file mode 100644
index 0000000..706a603
--- /dev/null
+++ b/common/src/com/android/tv/common/buildtype/BuildTypeFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.common.buildtype;
+
+import com.google.common.base.Supplier;
+
+import javax.inject.Inject;
+
+
+/** Factory for {@link HasBuildType.BuildType}.
+ *
+ * <p>Hardcoded to {@link HasBuildType.BuildType#AOSP}.
+ */
+public class BuildTypeFactory implements Supplier<HasBuildType> {
+ private static final HasBuildType INSTANCE = new AospBuildTypeProvider();
+
+ @Inject
+ public BuildTypeFactory() {}
+
+ public static HasBuildType create() {
+ return INSTANCE;
+ }
+
+ @Override
+ public HasBuildType get() {
+ return INSTANCE;
+ }
+}
\ No newline at end of file
diff --git a/common/src/com/android/tv/common/buildtype/BuildTypeModule.java b/common/src/com/android/tv/common/buildtype/BuildTypeModule.java
new file mode 100644
index 0000000..43f398d
--- /dev/null
+++ b/common/src/com/android/tv/common/buildtype/BuildTypeModule.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.common.buildtype;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Reusable;
+
+/** Provides BuildType */
+@Module
+public class BuildTypeModule {
+ private static final HasBuildType.BuildType BUILD_TYPE =
+ BuildTypeFactory.create().getBuildType();
+
+ @Provides
+ @Reusable
+ HasBuildType.BuildType providesBuildType() {
+ return BUILD_TYPE;
+ }
+}
diff --git a/common/src/com/android/tv/common/buildtype/HasBuildType.java b/common/src/com/android/tv/common/buildtype/HasBuildType.java
index 7d5677c..addac07 100644
--- a/common/src/com/android/tv/common/buildtype/HasBuildType.java
+++ b/common/src/com/android/tv/common/buildtype/HasBuildType.java
@@ -30,5 +30,7 @@
PROD
}
+ /** @deprecated use injection instead. */
+ @Deprecated
BuildType getBuildType();
}
diff --git a/common/src/com/android/tv/common/compat/TvInputInfoCompat.java b/common/src/com/android/tv/common/compat/TvInputInfoCompat.java
index 685a3ed..2f06d94 100644
--- a/common/src/com/android/tv/common/compat/TvInputInfoCompat.java
+++ b/common/src/com/android/tv/common/compat/TvInputInfoCompat.java
@@ -45,13 +45,12 @@
private final Context mContext;
private final TvInputInfo mTvInputInfo;
- private final boolean mAudioOnly;
+ private boolean mAudioOnly;
+ private boolean mAudioAttributeInit = false;
public TvInputInfoCompat(Context context, TvInputInfo tvInputInfo) {
mContext = context;
mTvInputInfo = tvInputInfo;
- // TODO(b/112938832): use tvInputInfo.isAudioOnly() when SDK is updated
- mAudioOnly = Boolean.parseBoolean(getExtras().get(ATTRIBUTE_NAME_AUDIO_ONLY));
}
public TvInputInfo getTvInputInfo() {
@@ -59,6 +58,11 @@
}
public boolean isAudioOnly() {
+ // TODO(b/112938832): use tvInputInfo.isAudioOnly() when SDK is updated
+ if (!mAudioAttributeInit) {
+ mAudioOnly = Boolean.parseBoolean(getExtras().get(ATTRIBUTE_NAME_AUDIO_ONLY));
+ mAudioAttributeInit = true;
+ }
return mAudioOnly;
}
diff --git a/common/src/com/android/tv/common/compat/internal/recording_commands.proto b/common/src/com/android/tv/common/compat/internal/recording_commands.proto
index ce59bfa..c247e78 100644
--- a/common/src/com/android/tv/common/compat/internal/recording_commands.proto
+++ b/common/src/com/android/tv/common/compat/internal/recording_commands.proto
@@ -19,6 +19,7 @@
// package and should not be used outside it.
syntax = "proto3";
+
package android.tv.common.compat.internal;
option java_outer_classname = "RecordingCommands";
diff --git a/common/src/com/android/tv/common/compat/internal/recording_events.proto b/common/src/com/android/tv/common/compat/internal/recording_events.proto
index 68db5dd..fffa62a 100644
--- a/common/src/com/android/tv/common/compat/internal/recording_events.proto
+++ b/common/src/com/android/tv/common/compat/internal/recording_events.proto
@@ -18,6 +18,7 @@
// support new features on older devices. NOTE: this proto is internal to this
// package and should not be used outside it.
syntax = "proto3";
+
package android.tv.common.compat.internal;
option java_outer_classname = "RecordingEvents";
@@ -46,4 +47,3 @@
// Recording URI.
string uri = 1;
}
-
diff --git a/common/src/com/android/tv/common/compat/internal/tif_commands.proto b/common/src/com/android/tv/common/compat/internal/tif_commands.proto
index d586770..b69d487 100644
--- a/common/src/com/android/tv/common/compat/internal/tif_commands.proto
+++ b/common/src/com/android/tv/common/compat/internal/tif_commands.proto
@@ -19,6 +19,7 @@
// package and should not be used outside it.
syntax = "proto3";
+
package android.tv.common.compat.internal;
option java_outer_classname = "Commands";
diff --git a/common/src/com/android/tv/common/compat/internal/tif_events.proto b/common/src/com/android/tv/common/compat/internal/tif_events.proto
index 6e71ae1..b15a884 100644
--- a/common/src/com/android/tv/common/compat/internal/tif_events.proto
+++ b/common/src/com/android/tv/common/compat/internal/tif_events.proto
@@ -18,6 +18,7 @@
// support new features on older devices. NOTE: this proto is internal to this
// package and should not be used outside it.
syntax = "proto3";
+
package android.tv.common.compat.internal;
option java_outer_classname = "Events";
diff --git a/common/src/com/android/tv/common/customization/CustomizationManager.java b/common/src/com/android/tv/common/customization/CustomizationManager.java
index 09ecaef..5a29d7c 100644
--- a/common/src/com/android/tv/common/customization/CustomizationManager.java
+++ b/common/src/com/android/tv/common/customization/CustomizationManager.java
@@ -97,8 +97,8 @@
/**
* Returns {@code true} if there's a customization package installed and it specifies built-in
- * tuner devices are available. The built-in tuner should support DVB API to be recognized by
- * Live TV.
+ * tuner devices are available. The built-in tuner should support DVB API to be recognized by TV
+ * app.
*/
public static boolean hasLinuxDvbBuiltInTuner(Context context) {
if (sHasLinuxDvbBuiltInTuner == null) {
@@ -156,11 +156,26 @@
private static String getCustomizationPackageName(Context context) {
if (sCustomizationPackage == null) {
+ sCustomizationPackage = "";
List<PackageInfo> packageInfos =
context.getPackageManager()
.getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0);
- sCustomizationPackage = packageInfos.size() == 0 ? "" : packageInfos.get(0).packageName;
+ if (packageInfos.size() != 0) {
+ /** Iterate through all packages returning the first vendor customizer */
+ for (PackageInfo packageInfo : packageInfos) {
+ if (packageInfo.packageName.startsWith("com.android") == false) {
+ sCustomizationPackage = packageInfo.packageName;
+ break;
+ }
+ }
+
+ /** If no vendor package found, return first in the list */
+ if (sCustomizationPackage == "") {
+ sCustomizationPackage = packageInfos.get(0).packageName;
+ }
+ }
}
+
return sCustomizationPackage;
}
diff --git a/common/src/com/android/tv/common/dagger/ApplicationModule.java b/common/src/com/android/tv/common/dagger/ApplicationModule.java
index 4655f77..be9cf88 100644
--- a/common/src/com/android/tv/common/dagger/ApplicationModule.java
+++ b/common/src/com/android/tv/common/dagger/ApplicationModule.java
@@ -21,8 +21,10 @@
import android.os.Looper;
import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.common.dagger.annotations.MainLooper;
+import com.android.tv.common.util.Clock;
import dagger.Module;
import dagger.Provides;
+import dagger.Reusable;
/**
* Provides application-scope qualifiers for the {@link Application}, the application context, and
@@ -57,4 +59,10 @@
ContentResolver provideContentResolver() {
return mApplication.getContentResolver();
}
+
+ @Provides
+ @Reusable
+ static Clock providesClock() {
+ return Clock.SYSTEM;
+ }
}
diff --git a/common/src/com/android/tv/common/dagger/init/SafePreDaggerInitializer.java b/common/src/com/android/tv/common/dagger/init/SafePreDaggerInitializer.java
new file mode 100644
index 0000000..9465d92
--- /dev/null
+++ b/common/src/com/android/tv/common/dagger/init/SafePreDaggerInitializer.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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.common.dagger.init;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * Initializes objects one time only.
+ *
+ * <p>This is needed because ContentProviders can be created before Application.onCreate
+ */
+public final class SafePreDaggerInitializer {
+ private interface Initialize {
+ void init(Context context);
+ }
+
+ private static final String TAG = "SafePreDaggerInitializer";
+
+ private static boolean initialized = false;
+ private static Context oldContext;
+
+ private static final Initialize[] sList =
+ new Initialize[] {
+ /* Begin_AOSP_Comment_Out
+ com.google.android.libraries.phenotype.client.PhenotypeContext::setContext
+ End_AOSP_Comment_Out */
+ };
+
+ public static synchronized void init(Context context) {
+ if (!initialized) {
+ for (Initialize i : sList) {
+ i.init(context);
+ }
+ oldContext = context;
+ initialized = true;
+ } else if (oldContext != context) {
+ Log.w(
+ TAG,
+ "init called more than once, skipping. Old context was "
+ + oldContext
+ + " new context is "
+ + context);
+ }
+ }
+
+ private SafePreDaggerInitializer() {}
+}
diff --git a/common/src/com/android/tv/common/dev/DeveloperPreference.java b/common/src/com/android/tv/common/dev/DeveloperPreference.java
new file mode 100644
index 0000000..b1c401b
--- /dev/null
+++ b/common/src/com/android/tv/common/dev/DeveloperPreference.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 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.common.dev;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+
+/** Preferences available to developers */
+public abstract class DeveloperPreference<T> {
+
+ private static final String PREFERENCE_FILE_NAME =
+ "com.android.tv.common.dev.DeveloperPreference";
+
+ /**
+ * Create a boolean developer preference.
+ *
+ * @param key the developer setting key.
+ * @param defaultValue the value to return if the setting is undefined or empty.
+ */
+ public static DeveloperPreference<Boolean> create(String key, boolean defaultValue) {
+ return new DeveloperBooleanPreference(key, defaultValue);
+ }
+
+ @VisibleForTesting
+ static final SharedPreferences getPreferences(Context context) {
+ return context.getSharedPreferences(PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Create a int developer preference.
+ *
+ * @param key the developer setting key.
+ * @param defaultValue the value to return if the setting is undefined or empty.
+ */
+ public static DeveloperPreference<Integer> create(String key, int defaultValue) {
+ return new DeveloperIntegerPreference(key, defaultValue);
+ }
+
+ final String mKey;
+ final T mDefaultValue;
+ private T mValue;
+
+ private DeveloperPreference(String key, T defaultValue) {
+ mKey = key;
+ mValue = null;
+ mDefaultValue = defaultValue;
+ }
+
+ /** Set the value. */
+ public final void set(Context context, T value) {
+ mValue = value;
+ storeValue(context, value);
+ }
+
+ protected abstract void storeValue(Context context, T value);
+
+ /** Get the current value, or the default if the value is not set. */
+ public final T get(Context context) {
+ mValue = getStoredValue(context);
+ return mValue;
+ }
+
+ /** Get the current value, or the default if the value is not set or context is null. */
+ public final T getDefaultIfContextNull(@Nullable Context context) {
+ return context == null ? mDefaultValue : getStoredValue(context);
+ }
+
+ protected abstract T getStoredValue(Context context);
+
+ /**
+ * Clears the current value.
+ *
+ * <p>Future calls to {@link #get(Context)} will return the default value.
+ */
+ public final void clear(Context context) {
+ getPreferences(context).edit().remove(mKey);
+ }
+
+ @Override
+ public final String toString() {
+ return "[" + mKey + "]=" + mValue + " Default value : " + mDefaultValue;
+ }
+
+ private static final class DeveloperBooleanPreference extends DeveloperPreference<Boolean> {
+
+ private DeveloperBooleanPreference(String key, Boolean defaultValue) {
+ super(key, defaultValue);
+ }
+
+ @Override
+ public void storeValue(Context context, Boolean value) {
+ getPreferences(context).edit().putBoolean(mKey, value).apply();
+ }
+
+ @Override
+ public Boolean getStoredValue(Context context) {
+ return getPreferences(context).getBoolean(mKey, mDefaultValue);
+ }
+ }
+
+ private static final class DeveloperIntegerPreference extends DeveloperPreference<Integer> {
+
+ private DeveloperIntegerPreference(String key, Integer defaultValue) {
+ super(key, defaultValue);
+ }
+
+ @Override
+ protected void storeValue(Context context, Integer value) {
+ getPreferences(context).edit().putInt(mKey, value).apply();
+ }
+
+ @Override
+ protected Integer getStoredValue(Context context) {
+ return getPreferences(context).getInt(mKey, mDefaultValue);
+ }
+ }
+}
diff --git a/common/src/com/android/tv/common/dev/DeveloperPreferences.java b/common/src/com/android/tv/common/dev/DeveloperPreferences.java
new file mode 100644
index 0000000..9c83b64
--- /dev/null
+++ b/common/src/com/android/tv/common/dev/DeveloperPreferences.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.common.dev;
+
+/** A class about the constants for TV Developer preferences. */
+public final class DeveloperPreferences {
+
+ /**
+ * Allow Google Analytics for eng builds.
+ *
+ * <p>Defaults to {@code false}.
+ */
+ public static final DeveloperPreference<Boolean> ALLOW_ANALYTICS_IN_ENG =
+ DeveloperPreference.create("tv_allow_analytics_in_eng", false);
+
+ /**
+ * Allow Strict mode for debug builds.
+ *
+ * <p>Defaults to {@code true}.
+ */
+ public static final DeveloperPreference<Boolean> ALLOW_STRICT_MODE =
+ DeveloperPreference.create("tv_allow_strict_mode", true);
+
+ /**
+ * When true {@link android.view.KeyEvent}s are logged.
+ *
+ * <p>Defaults to {@code false}.
+ */
+ public static final DeveloperPreference<Boolean> LOG_KEYEVENT =
+ DeveloperPreference.create("tv_log_keyevent", false);
+
+ /**
+ * When true debug keys are used.
+ *
+ * <p>Defaults to {@code false}.
+ */
+ public static final DeveloperPreference<Boolean> USE_DEBUG_KEYS =
+ DeveloperPreference.create("tv_use_debug_keys", false);
+
+ /**
+ * Send {@link com.android.tv.analytics.Tracker} information.
+ *
+ * <p>Defaults to {@code true}.
+ */
+ public static final DeveloperPreference<Boolean> USE_TRACKER =
+ DeveloperPreference.create("tv_use_tracker", true);
+
+ /**
+ * Maximum buffer size in MegaBytes.
+ *
+ * <p>Defaults to 2MB.
+ */
+ public static final DeveloperPreference<Integer> MAX_BUFFER_SIZE_MBYTES =
+ DeveloperPreference.create("tv.tuner.buffersize_mbytes", 2 * 1024);
+
+ private DeveloperPreferences() {}
+}
diff --git a/common/src/com/android/tv/common/experiments/ExperimentFlag.java b/common/src/com/android/tv/common/experiments/ExperimentFlag.java
deleted file mode 100644
index b8370ad..0000000
--- a/common/src/com/android/tv/common/experiments/ExperimentFlag.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2016 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.common.experiments;
-
-import android.support.annotation.VisibleForTesting;
-import com.android.tv.common.BuildConfig;
-
-import com.google.common.base.Supplier;
-
-/** Experiments return values based on user, device and other criteria. */
-public final class ExperimentFlag<T> {
-
- // NOTE: sAllowOverrides IS NEVER USED in the non AOSP version.
- private static boolean sAllowOverrides = false;
-
- @VisibleForTesting
- public static void initForTest() {
- /* Begin_AOSP_Comment_Out
- if (!BuildConfig.AOSP) {
- PhenotypeFlag.initForTest();
- return;
- }
- End_AOSP_Comment_Out */
- sAllowOverrides = true;
- }
-
- /** Returns a boolean experiment */
- public static ExperimentFlag<Boolean> createFlag(
-// AOSP_Comment_Out Supplier<Boolean> phenotypeFlag,
- boolean defaultValue) {
- return new ExperimentFlag<>(
-// AOSP_Comment_Out phenotypeFlag,
- defaultValue);
- }
-
- private final T mDefaultValue;
-// AOSP_Comment_Out private final Supplier<T> mPhenotypeFlag;
-
-// AOSP_Comment_Out // NOTE: mOverrideValue IS NEVER USED in the non AOSP version.
- private T mOverrideValue = null;
- // mOverridden IS NEVER USED in the non AOSP version.
- private boolean mOverridden = false;
-
- private ExperimentFlag(
-// AOSP_Comment_Out Supplier<T> phenotypeFlag,
- // NOTE: defaultValue IS NEVER USED in the non AOSP version.
- T defaultValue) {
- mDefaultValue = defaultValue;
-// AOSP_Comment_Out mPhenotypeFlag = phenotypeFlag;
- }
-
- /** Returns value for this experiment */
- public T get() {
- /* Begin_AOSP_Comment_Out
- if (!BuildConfig.AOSP) {
- return mPhenotypeFlag.get();
- }
- End_AOSP_Comment_Out */
- return sAllowOverrides && mOverridden ? mOverrideValue : mDefaultValue;
- }
-
- @VisibleForTesting
- public void override(T t) {
-
- if (sAllowOverrides) {
- mOverridden = true;
- mOverrideValue = t;
- }
- }
-
- @VisibleForTesting
- public void resetOverride() {
- mOverridden = false;
- }
-
- /* Begin_AOSP_Comment_Out
- @VisibleForTesting
- T getAospDefaultValueForTesting() {
- return mDefaultValue;
- }
- End_AOSP_Comment_Out */
-}
diff --git a/common/src/com/android/tv/common/experiments/ExperimentLoader.java b/common/src/com/android/tv/common/experiments/ExperimentLoader.java
deleted file mode 100644
index 5f012e1..0000000
--- a/common/src/com/android/tv/common/experiments/ExperimentLoader.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 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.common.experiments;
-
-import android.content.Context;
-
-/** Used to sync {@link ExperimentFlag}s. */
-public class ExperimentLoader {
-
- /** Starts a background task to update {@link ExperimentFlag}s */
- public void asyncRefreshExperiments(Context context) {
- // Override for your experiment system
- }
-}
diff --git a/common/src/com/android/tv/common/experiments/Experiments.java b/common/src/com/android/tv/common/experiments/Experiments.java
deleted file mode 100644
index 9bfdb54..0000000
--- a/common/src/com/android/tv/common/experiments/Experiments.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2016 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.common.experiments;
-
-import static com.android.tv.common.experiments.ExperimentFlag.createFlag;
-
-import com.android.tv.common.BuildConfig;
-// AOSP_Comment_Out import com.android.tv.common.flags.LiveChannels;
-
-/**
- * Set of experiments visible in AOSP.
- *
- * <p>This file is maintained by hand.
- */
-public final class Experiments {
- public static final ExperimentFlag<Boolean> ENABLE_UNRATED_CONTENT_SETTINGS =
- ExperimentFlag.createFlag(
-// AOSP_Comment_Out LiveChannels::enableUnratedContentSettings,
- false);
-
- /** Turn analytics on or off based on the System Checkbox for logging. */
- public static final ExperimentFlag<Boolean> ENABLE_ANALYTICS_VIA_CHECKBOX =
- createFlag(
-// AOSP_Comment_Out LiveChannels::enableAnalyticsViaCheckbox,
- false);
-
- /**
- * Allow developer features such as the dev menu and other aids.
- *
- * <p>These features are available to select users(aka fishfooders) on production builds.
- */
- public static final ExperimentFlag<Boolean> ENABLE_DEVELOPER_FEATURES =
- ExperimentFlag.createFlag(
-// AOSP_Comment_Out LiveChannels::enableDeveloperFeatures,
- BuildConfig.ENG);
-
- /**
- * Allow QA features.
- *
- * <p>These features must be carefully limited, keeping QA differences to a minimum.
- *
- * <p>These features are available to select users(aka QA) on production builds.
- */
- public static final ExperimentFlag<Boolean> ENABLE_QA_FEATURES =
- ExperimentFlag.createFlag(
-// AOSP_Comment_Out LiveChannels::enableQaFeatures,
- false);
-
- private Experiments() {}
-}
diff --git a/common/src/com/android/tv/common/feature/CommonFeatures.java b/common/src/com/android/tv/common/feature/CommonFeatures.java
index 04052a7..abe4c1d 100644
--- a/common/src/com/android/tv/common/feature/CommonFeatures.java
+++ b/common/src/com/android/tv/common/feature/CommonFeatures.java
@@ -23,12 +23,14 @@
import android.content.Context;
import android.util.Log;
+
import com.android.tv.common.flags.has.HasCloudEpgFlags;
import com.android.tv.common.util.LocationUtils;
+
import com.android.tv.common.flags.CloudEpgFlags;
/**
- * List of {@link Feature} that affect more than just the Live TV app.
+ * List of {@link Feature} that affect more than just the TV app.
*
* <p>Remove the {@code Feature} once it is launched.
*/
@@ -52,7 +54,7 @@
* <p>Enables dvr recording regardless of storage status.
*/
public static final Feature FORCE_RECORDING_UNTIL_NO_SPACE =
- PropertyFeature.create("force_recording_until_no_space", false);
+ DeveloperPreferenceFeature.create("force_recording_until_no_space", false);
/** Show postal code fragment before channel scan. */
public static final Feature ENABLE_CLOUD_EPG_REGION =
diff --git a/common/src/com/android/tv/common/feature/DeveloperPreferenceFeature.java b/common/src/com/android/tv/common/feature/DeveloperPreferenceFeature.java
new file mode 100644
index 0000000..1f98547
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/DeveloperPreferenceFeature.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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.common.feature;
+
+import android.content.Context;
+
+import com.android.tv.common.dev.DeveloperPreference;
+
+/** A {@link Feature} based on {@link DeveloperPreference<Boolean>}. */
+public class DeveloperPreferenceFeature implements Feature {
+
+ private final DeveloperPreference<Boolean> mPreference;
+
+ /**
+ * Create a developer preference feature.
+ *
+ * @param key the developer setting key.
+ * @param defaultValue the value to return if the setting is undefined or empty.
+ */
+ public static DeveloperPreferenceFeature create(String key, boolean defaultValue) {
+ return from(DeveloperPreference.create(key, defaultValue));
+ }
+
+ /**
+ * Create a developer preference feature from an exiting {@link DeveloperPreference<Boolean>}.
+ */
+ public static DeveloperPreferenceFeature from(
+ DeveloperPreference<Boolean> developerPreference) {
+ return new DeveloperPreferenceFeature(developerPreference);
+ }
+
+ private DeveloperPreferenceFeature(DeveloperPreference<Boolean> mPreference) {
+ this.mPreference = mPreference;
+ }
+
+ @Override
+ public boolean isEnabled(Context context) {
+ return mPreference.get(context);
+ }
+
+ @Override
+ public String toString() {
+ return mPreference.toString();
+ }
+}
diff --git a/common/src/com/android/tv/common/feature/ExperimentFeature.java b/common/src/com/android/tv/common/feature/ExperimentFeature.java
deleted file mode 100644
index 820eda4..0000000
--- a/common/src/com/android/tv/common/feature/ExperimentFeature.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.common.feature;
-
-import android.content.Context;
-import com.android.tv.common.experiments.ExperimentFlag;
-
-/** A {@link Feature} base on an {@link ExperimentFlag}. */
-public final class ExperimentFeature implements Feature {
-
- public static Feature from(ExperimentFlag<Boolean> flag) {
- return new ExperimentFeature(flag);
- }
-
- private final ExperimentFlag<Boolean> mFlag;
-
- private ExperimentFeature(ExperimentFlag<Boolean> flag) {
- mFlag = flag;
- }
-
- @Override
- public boolean isEnabled(Context context) {
- return mFlag.get();
- }
-
- @Override
- public String toString() {
- return "ExperimentFeature for " + mFlag;
- }
-}
diff --git a/common/src/com/android/tv/common/feature/FeatureUtils.java b/common/src/com/android/tv/common/feature/FeatureUtils.java
index aaed6c8..e6192cd 100644
--- a/common/src/com/android/tv/common/feature/FeatureUtils.java
+++ b/common/src/com/android/tv/common/feature/FeatureUtils.java
@@ -17,7 +17,6 @@
package com.android.tv.common.feature;
import android.content.Context;
-import com.android.tv.common.BuildConfig;
import com.android.tv.common.util.CommonUtils;
import java.util.Arrays;
@@ -71,23 +70,6 @@
}
};
}
- /**
- * A feature available in AOSP.
- *
- * @param googleFeature the feature used in non AOSP builds
- * @param aospFeature the feature used in AOSP builds
- */
- public static Feature aospFeature(
-// AOSP_Comment_Out final Feature googleFeature,
- final Feature aospFeature) {
- /* Begin_AOSP_Comment_Out
- if (!BuildConfig.AOSP) {
- return googleFeature;
- } else {
- End_AOSP_Comment_Out */
- return aospFeature;
-// AOSP_Comment_Out }
- }
/**
* Returns a feature that is opposite of the given {@code feature}.
diff --git a/common/src/com/android/tv/common/feature/Model.java b/common/src/com/android/tv/common/feature/Model.java
index 7aa5148..450cd21 100644
--- a/common/src/com/android/tv/common/feature/Model.java
+++ b/common/src/com/android/tv/common/feature/Model.java
@@ -21,10 +21,11 @@
/** Holder for {@link android.os.Build#MODEL} features. */
public interface Model {
+ ModelFeature ARCHER = new ModelFeature("Archer");
ModelFeature NEXUS_PLAYER = new ModelFeature("Nexus Player");
/** True when the {@link android.os.Build#MODEL} equals the {@code model} given. */
- public static final class ModelFeature implements Feature {
+ final class ModelFeature implements Feature {
private final String mModel;
private ModelFeature(String model) {
diff --git a/common/src/com/android/tv/common/feature/PermissionFeature.java b/common/src/com/android/tv/common/feature/PermissionFeature.java
new file mode 100644
index 0000000..0261178
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/PermissionFeature.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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.common.feature;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+/** A feature that is only available when {@code permissionName} is granted. */
+public class PermissionFeature implements Feature {
+
+ public static final PermissionFeature DVB_DEVICE_PERMISSION =
+ new PermissionFeature("android.permission.DVB_DEVICE");
+
+ private final String permissionName;
+
+ private PermissionFeature(String permissionName) {
+ this.permissionName = permissionName;
+ }
+
+ @Override
+ public boolean isEnabled(Context context) {
+ return context.checkSelfPermission(permissionName) == PackageManager.PERMISSION_GRANTED;
+ }
+}
diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java
index 4b0a925..54bc1bb 100644
--- a/common/src/com/android/tv/common/feature/Sdk.java
+++ b/common/src/com/android/tv/common/feature/Sdk.java
@@ -29,8 +29,6 @@
public static final Feature AT_LEAST_O = new AtLeast(VERSION_CODES.O);
- public static final Feature AT_LEAST_P = new AtLeast(VERSION_CODES.P); // AOSP_OC:strip_line
-
private static final class AtLeast implements Feature {
private final int versionCode;
diff --git a/common/src/com/android/tv/common/flags/BackendKnobsFlags.java b/common/src/com/android/tv/common/flags/BackendKnobsFlags.java
index 69bac7a..c6272c0 100644
--- a/common/src/com/android/tv/common/flags/BackendKnobsFlags.java
+++ b/common/src/com/android/tv/common/flags/BackendKnobsFlags.java
@@ -26,18 +26,12 @@
*/
boolean compiled();
- /** Enable fetching only part of the program data. */
- boolean enablePartialProgramFetch();
-
/** EPG fetcher interval in hours */
long epgFetcherIntervalHour();
/** Target channel count for EPG. It is used to adjust the EPG length */
long epgTargetChannelCount();
- /** Enables fetching a few hours of programs only when the epg is scrolled to that time. */
- boolean fetchProgramsAsNeeded();
-
/** How many hours of programs are loaded in the program guide for during the initial fetch */
long programGuideInitialFetchHours();
diff --git a/common/src/com/android/tv/common/flags/CloudEpgFlags.java b/common/src/com/android/tv/common/flags/CloudEpgFlags.java
index ab4c6a1..48b950b 100755
--- a/common/src/com/android/tv/common/flags/CloudEpgFlags.java
+++ b/common/src/com/android/tv/common/flags/CloudEpgFlags.java
@@ -29,6 +29,6 @@
/** Is the device in a region supported by Cloud Epg */
boolean supportedRegion();
- /** List of input ids that Live TV will update their EPG. */
+ /** List of input ids that the TV app will update their EPG. */
String thirdPartyEpgInputsCsv();
}
diff --git a/common/src/com/android/tv/common/flags/ConcurrentDvrPlaybackFlags.java b/common/src/com/android/tv/common/flags/ConcurrentDvrPlaybackFlags.java
index 1afff79..42892e0 100755
--- a/common/src/com/android/tv/common/flags/ConcurrentDvrPlaybackFlags.java
+++ b/common/src/com/android/tv/common/flags/ConcurrentDvrPlaybackFlags.java
@@ -26,9 +26,6 @@
*/
boolean compiled();
- /** Enable playback of DVR playback during recording */
- boolean enabled();
-
/** Enable tuner using recording data for playback in onTune */
boolean onTuneUsesRecording();
}
diff --git a/common/src/com/android/tv/common/flags/DvrFlags.java b/common/src/com/android/tv/common/flags/DvrFlags.java
new file mode 100755
index 0000000..2e1d531
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/DvrFlags.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.common.flags;
+
+/** DVR flags */
+public interface DvrFlags {
+
+ /**
+ * Whether or not this feature is compiled into this build.
+ *
+ * <p>This returns true by default, unless the is_compiled_selector parameter was set during
+ * code generation.
+ */
+ boolean compiled();
+
+ /** Allow user to customize timings of program recordings. */
+ boolean startEarlyEndLateEnabled();
+
+ /** Store and use the video aspect ratio in recordings. */
+ boolean storeVideoAspectRatio();
+}
diff --git a/common/src/com/android/tv/common/flags/LegacyFlags.java b/common/src/com/android/tv/common/flags/LegacyFlags.java
new file mode 100755
index 0000000..dbccf70
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/LegacyFlags.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 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.common.flags;
+
+/** Legacy flags */
+public interface LegacyFlags {
+
+ /**
+ * Whether or not this feature is compiled into this build.
+ *
+ * <p>This returns true by default, unless the is_compiled_selector parameter was set during
+ * code generation.
+ */
+ boolean compiled();
+
+ /** Enable Developer Features */
+ boolean enableDeveloperFeatures();
+
+ /** Enable QA Features */
+ boolean enableQaFeatures();
+
+ /** Enable Unrated Content Settings */
+ boolean enableUnratedContentSettings();
+}
diff --git a/common/src/com/android/tv/common/flags/MessagesFlags.java b/common/src/com/android/tv/common/flags/MessagesFlags.java
new file mode 100755
index 0000000..b5411d7
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/MessagesFlags.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 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.common.flags;
+
+/**
+ * Message flags.
+ *
+ * <p>Used to hide new messages until all translations are ready.
+ *
+ * <p>Production releases never include the messages protected by these flags.
+ */
+public interface MessagesFlags {
+
+ /**
+ * Whether or not this feature is compiled into this build.
+ *
+ * <p>This returns true by default, unless the is_compiled_selector parameter was set during
+ * code generation.
+ */
+ boolean compiled();
+
+ /** Use setup_sources_description2 */
+ boolean setupSourcesDescription2();
+}
diff --git a/common/src/com/android/tv/common/flags/StartupFlags.java b/common/src/com/android/tv/common/flags/StartupFlags.java
new file mode 100755
index 0000000..e6f6837
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/StartupFlags.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.common.flags;
+
+/** Flags for TV App startup */
+public interface StartupFlags {
+
+ /**
+ * Whether or not this feature is compiled into this build.
+ *
+ * <p>This returns true by default, unless the is_compiled_selector parameter was set during
+ * code generation.
+ */
+ boolean compiled();
+
+ /** InputId's that will not be warmed up on MainActivity creation. */
+ com.android.tv.common.flags.proto.TypedFeatures.StringListParam warmupInputidBlacklist();
+}
diff --git a/common/src/com/android/tv/common/flags/TunerFlags.java b/common/src/com/android/tv/common/flags/TunerFlags.java
index 5f899b9..5be7b79 100755
--- a/common/src/com/android/tv/common/flags/TunerFlags.java
+++ b/common/src/com/android/tv/common/flags/TunerFlags.java
@@ -26,9 +26,6 @@
*/
boolean compiled();
- /** Tune using current recording if available. */
- boolean tuneUsingRecording();
-
/** Enable using exoplayer V2 */
boolean useExoplayerV2();
}
diff --git a/common/src/com/android/tv/common/flags/UiFlags.java b/common/src/com/android/tv/common/flags/UiFlags.java
index 4c88d08..73349be 100755
--- a/common/src/com/android/tv/common/flags/UiFlags.java
+++ b/common/src/com/android/tv/common/flags/UiFlags.java
@@ -15,7 +15,7 @@
*/
package com.android.tv.common.flags;
-/** Flags for Live TV UI */
+/** Flags for TV app UI */
public interface UiFlags {
/**
@@ -26,6 +26,9 @@
*/
boolean compiled();
+ /** Critic Ratings */
+ boolean enableCriticRatings();
+
/**
* Number of days to be shown by Recording History.
*
@@ -35,7 +38,4 @@
/** Unhide the launcher all the time */
boolean uhideLauncher();
-
- /** Use the Leanback Pin Picker */
- boolean useLeanbackPinPicker();
}
diff --git a/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java b/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java
deleted file mode 100644
index b471087..0000000
--- a/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2018 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.common.flags.has;
-
-import android.content.Context;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
-
-/** Has {@link ConcurrentDvrPlaybackFlags} */
-public interface HasConcurrentDvrPlaybackFlags {
-
- static ConcurrentDvrPlaybackFlags fromContext(Context context) {
- return ((HasConcurrentDvrPlaybackFlags) HasUtils.getApplicationContext(context))
- .getConcurrentDvrPlaybackFlags();
- }
-
- ConcurrentDvrPlaybackFlags getConcurrentDvrPlaybackFlags();
-}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java
index a189e47..cc6612f 100644
--- a/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java
+++ b/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java
@@ -25,23 +25,13 @@
}
@Override
- public boolean enablePartialProgramFetch() {
- return false;
- }
-
- @Override
public long epgFetcherIntervalHour() {
return 25;
}
@Override
- public boolean fetchProgramsAsNeeded() {
- return false;
- }
-
- @Override
public long programGuideInitialFetchHours() {
- return 8;
+ return 4;
}
@Override
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java
index 8d8c584..ee470ca 100644
--- a/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java
+++ b/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java
@@ -26,11 +26,6 @@
}
@Override
- public boolean enabled() {
- return false;
- }
-
- @Override
public boolean onTuneUsesRecording() {
return false;
}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultDvrFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultDvrFlags.java
new file mode 100644
index 0000000..09f7b4f
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultDvrFlags.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 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.common.flags.impl;
+
+/** Flags for tuning non ui behavior. */
+public final class DefaultDvrFlags
+ implements com.android.tv.common.flags.DvrFlags {
+
+ @Override
+ public boolean compiled() {
+ return true;
+ }
+
+ @Override
+ public boolean startEarlyEndLateEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean storeVideoAspectRatio() {
+ return false;
+ }
+}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultFlagsModule.java b/common/src/com/android/tv/common/flags/impl/DefaultFlagsModule.java
index 4935236..2aaf446 100644
--- a/common/src/com/android/tv/common/flags/impl/DefaultFlagsModule.java
+++ b/common/src/com/android/tv/common/flags/impl/DefaultFlagsModule.java
@@ -21,6 +21,9 @@
import com.android.tv.common.flags.BackendKnobsFlags;
import com.android.tv.common.flags.CloudEpgFlags;
import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import com.android.tv.common.flags.DvrFlags;
+import com.android.tv.common.flags.LegacyFlags;
+import com.android.tv.common.flags.StartupFlags;
import com.android.tv.common.flags.TunerFlags;
import com.android.tv.common.flags.UiFlags;
@@ -48,6 +51,24 @@
@Provides
@Reusable
+ DvrFlags provideDvrFlags() {
+ return new DefaultDvrFlags();
+ }
+
+ @Provides
+ @Reusable
+ LegacyFlags provideLegacyFlags() {
+ return DefaultLegacyFlags.DEFAULT;
+ }
+
+ @Provides
+ @Reusable
+ StartupFlags provideStartupFlags() {
+ return new DefaultStartupFlags();
+ }
+
+ @Provides
+ @Reusable
TunerFlags provideTunerFlags() {
return new DefaultTunerFlags();
}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultLegacyFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultLegacyFlags.java
new file mode 100644
index 0000000..5214241
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultLegacyFlags.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.common.flags.impl;
+
+import com.google.auto.value.AutoValue;
+import com.android.tv.common.flags.LegacyFlags;
+
+/** Default {@link LegacyFlags}. */
+@AutoValue
+public abstract class DefaultLegacyFlags implements LegacyFlags {
+ public static final DefaultLegacyFlags DEFAULT = DefaultLegacyFlags.builder().build();
+
+ public static Builder builder() {
+ return new AutoValue_DefaultLegacyFlags.Builder()
+ .compiled(true)
+ .enableDeveloperFeatures(false)
+ .enableQaFeatures(false)
+ .enableUnratedContentSettings(false);
+ }
+
+ /** Builder for {@link LegacyFlags} */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder compiled(boolean value);
+
+ public abstract Builder enableDeveloperFeatures(boolean value);
+
+ public abstract Builder enableQaFeatures(boolean value);
+
+ public abstract Builder enableUnratedContentSettings(boolean value);
+
+ public abstract DefaultLegacyFlags build();
+ }
+}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultMessagesFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultMessagesFlags.java
new file mode 100644
index 0000000..091f422
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultMessagesFlags.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.common.flags.impl;
+
+/**
+ * Default flag values for {@link
+ * com.android.tv.common.flags.MessagesFlags}.
+ */
+public final class DefaultMessagesFlags
+ implements com.android.tv.common.flags.MessagesFlags {
+
+ @Override
+ public boolean compiled() {
+ return true;
+ }
+
+ @Override
+ public boolean setupSourcesDescription2() {
+ return false;
+ }
+}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultStartupFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultStartupFlags.java
new file mode 100644
index 0000000..3eb6edc
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultStartupFlags.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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.common.flags.impl;
+
+import com.android.tv.common.flags.proto.TypedFeatures.StringListParam;
+import com.android.tv.common.flags.StartupFlags;
+
+/** Default {@link StartupFlags} */
+public class DefaultStartupFlags implements StartupFlags {
+ @Override
+ public boolean compiled() {
+ return true;
+ }
+
+ @Override
+ public StringListParam warmupInputidBlacklist() {
+ return StringListParam.getDefaultInstance();
+ }
+}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java
index 195953b..2d12e36 100644
--- a/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java
+++ b/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java
@@ -26,11 +26,6 @@
}
@Override
- public boolean tuneUsingRecording() {
- return false;
- }
-
- @Override
public boolean useExoplayerV2() {
return false;
}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultUiFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultUiFlags.java
index fce4585..2746485 100644
--- a/common/src/com/android/tv/common/flags/impl/DefaultUiFlags.java
+++ b/common/src/com/android/tv/common/flags/impl/DefaultUiFlags.java
@@ -17,7 +17,7 @@
import com.android.tv.common.flags.UiFlags;
-/** Default Flags for Live TV UI */
+/** Default Flags for TV app UI */
public class DefaultUiFlags implements UiFlags {
@Override
@@ -26,17 +26,17 @@
}
@Override
+ public boolean enableCriticRatings() {
+ return false;
+ }
+
+ @Override
public boolean uhideLauncher() {
return false;
}
@Override
- public boolean useLeanbackPinPicker() {
- return false;
- }
-
- @Override
public long maxHistoryDays() {
- return 7;
+ return 0;
}
}
diff --git a/common/src/com/android/tv/common/flags/impl/SettableFlagsModule.java b/common/src/com/android/tv/common/flags/impl/SettableFlagsModule.java
new file mode 100644
index 0000000..b188158
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/SettableFlagsModule.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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.common.flags.impl;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Reusable;
+import com.android.tv.common.flags.BackendKnobsFlags;
+import com.android.tv.common.flags.CloudEpgFlags;
+import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import com.android.tv.common.flags.DvrFlags;
+import com.android.tv.common.flags.LegacyFlags;
+import com.android.tv.common.flags.StartupFlags;
+import com.android.tv.common.flags.TunerFlags;
+import com.android.tv.common.flags.UiFlags;
+
+/** Provides public fields for each flag so they can be changed before injection. */
+@Module
+public class SettableFlagsModule {
+
+ public DefaultBackendKnobsFlags backendKnobsFlags = new DefaultBackendKnobsFlags();
+ public DefaultCloudEpgFlags cloudEpgFlags = new DefaultCloudEpgFlags();
+ public DefaultConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags =
+ new DefaultConcurrentDvrPlaybackFlags();
+ public DefaultDvrFlags dvrFlags = new DefaultDvrFlags();
+ public DefaultLegacyFlags legacyFlags = DefaultLegacyFlags.DEFAULT;
+ public DefaultStartupFlags startupFlags = new DefaultStartupFlags();
+ public DefaultTunerFlags tunerFlags = new DefaultTunerFlags();
+ public DefaultUiFlags uiFlags = new DefaultUiFlags();
+
+ @Provides
+ @Reusable
+ BackendKnobsFlags provideBackendKnobsFlags() {
+ return backendKnobsFlags;
+ }
+
+ @Provides
+ @Reusable
+ CloudEpgFlags provideCloudEpgFlags() {
+ return cloudEpgFlags;
+ }
+
+ @Provides
+ @Reusable
+ ConcurrentDvrPlaybackFlags provideConcurrentDvrPlaybackFlags() {
+ return concurrentDvrPlaybackFlags;
+ }
+
+ @Provides
+ @Reusable
+ DvrFlags provideDvrFlags() {
+ return dvrFlags;
+ }
+
+ @Provides
+ @Reusable
+ LegacyFlags provideLegacyFlags() {
+ return legacyFlags;
+ }
+
+ @Provides
+ @Reusable
+ StartupFlags provideStartupFlags() {
+ return startupFlags;
+ }
+
+ @Provides
+ @Reusable
+ TunerFlags provideTunerFlags() {
+ return tunerFlags;
+ }
+
+ @Provides
+ @Reusable
+ UiFlags provideUiFlags() {
+ return uiFlags;
+ }
+}
diff --git a/common/src/com/android/tv/common/flags/proto/typed-features.proto b/common/src/com/android/tv/common/flags/proto/typed-features.proto
new file mode 100644
index 0000000..855d731
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/proto/typed-features.proto
@@ -0,0 +1,20 @@
+syntax = "proto2";
+
+package android.tv.common.flags;
+
+option java_outer_classname = "TypedFeatures";
+option java_package = "com.android.tv.common.flags.proto";
+
+// These messages are to specify feature params that are a list of integers.
+message Int32ListParam {
+ repeated int32 element = 1;
+}
+
+message Int64ListParam {
+ repeated int64 element = 1;
+}
+
+// This message is to specify feature params that are a list of strings.
+message StringListParam {
+ repeated string element = 1;
+}
diff --git a/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java
index 0fb864b..3552a66 100644
--- a/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java
+++ b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java
@@ -28,8 +28,11 @@
import android.support.annotation.IntDef;
import android.support.annotation.WorkerThread;
import android.util.Log;
+
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.common.feature.CommonFeatures;
+
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -38,10 +41,13 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/** Signals DVR storage status change such as plugging/unplugging. */
+@Singleton
public class RecordingStorageStatusManager {
private static final String TAG = "RecordingStorageStatusManager";
- private static final boolean DEBUG = false;
/** Minimum storage size to support DVR */
public static final long MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES = 50 * 1024 * 1024 * 1024L; // 50GB
@@ -143,7 +149,8 @@
*
* @param context {@link Context}
*/
- public RecordingStorageStatusManager(final Context context) {
+ @Inject
+ public RecordingStorageStatusManager(@ApplicationContext Context context) {
mContext = context;
mMountedStorageStatus = getStorageStatusInternal();
mStorageValid = mMountedStorageStatus.isValidForDvr();
diff --git a/common/src/com/android/tv/common/singletons/HasTvInputId.java b/common/src/com/android/tv/common/singletons/HasTvInputId.java
index 4bc0a21..49cf3d2 100644
--- a/common/src/com/android/tv/common/singletons/HasTvInputId.java
+++ b/common/src/com/android/tv/common/singletons/HasTvInputId.java
@@ -18,8 +18,8 @@
/**
* Has TunerInputId.
*
- * <p>This is used buy both the tuner to get its input id and by the Live TV to get the
- * embedded tuner input id.
+ * <p>This is used buy both the tuner to get its input id and by the TV app to get the embedded
+ * tuner input id.
*/
public interface HasTvInputId {
diff --git a/common/src/com/android/tv/common/support/tvprovider/README.md b/common/src/com/android/tv/common/support/tvprovider/README.md
new file mode 100644
index 0000000..a24dc28
--- /dev/null
+++ b/common/src/com/android/tv/common/support/tvprovider/README.md
@@ -0,0 +1,6 @@
+## support provider
+
+This is preview code destined to be put in androidx.tvprovider.media.tv
+
+
+All classes here must have an associated bug to move to androidx
diff --git a/common/src/com/android/tv/common/support/tvprovider/TvContractCompatX.java b/common/src/com/android/tv/common/support/tvprovider/TvContractCompatX.java
new file mode 100644
index 0000000..353e342
--- /dev/null
+++ b/common/src/com/android/tv/common/support/tvprovider/TvContractCompatX.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 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.common.support.tvprovider;
+
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import androidx.tvprovider.media.tv.TvContractCompat;
+
+/**
+ * Extensions to the contract between the TV provider and applications. Contains definitions for the
+ * supported URIs and columns.
+ *
+ * <p>TODO(b/126921088): move this to androidx.
+ */
+public final class TvContractCompatX {
+
+ /**
+ * Builds a URI that points to a specific channel.
+ *
+ * @param inputPackage the package of the input.
+ * @param internalProviderId the internal provider id
+ */
+ public static Uri buildChannelUri(
+ @Nullable String inputPackage, @Nullable String internalProviderId) {
+ Uri.Builder uri = TvContractCompat.Channels.CONTENT_URI.buildUpon();
+ if (inputPackage != null) {
+ uri.appendQueryParameter("package", inputPackage);
+ }
+ if (internalProviderId != null) {
+ uri.appendQueryParameter(
+ TvContractCompat.Channels.COLUMN_INTERNAL_PROVIDER_ID, internalProviderId);
+ }
+ return uri.build();
+ }
+
+ /**
+ * Builds a URI that points to all programs on a given channel.
+ *
+ * @param inputPackage the package of the input.
+ * @param internalProviderId the internal provider id
+ */
+ public static Uri buildProgramsUriForChannel(
+ @Nullable String inputPackage, @Nullable String internalProviderId) {
+ Uri.Builder uri = TvContractCompat.Programs.CONTENT_URI.buildUpon();
+ if (inputPackage != null) {
+ uri.appendQueryParameter("package", inputPackage);
+ }
+ if (internalProviderId != null) {
+ uri.appendQueryParameter(
+ TvContractCompat.Channels.COLUMN_INTERNAL_PROVIDER_ID, internalProviderId);
+ }
+ return uri.build();
+ }
+
+ /**
+ * Builds a URI that points to programs on a specific channel whose schedules overlap with the
+ * given time frame.
+ *
+ * @param inputPackage the package of the input.
+ * @param internalProviderId the internal provider id
+ * @param startTime The start time used to filter programs. The returned programs should have
+ * {@link TvContractCompat.Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than this
+ * time.
+ * @param endTime The end time used to filter programs. The returned programs should have {@link
+ * TvContractCompat.Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than this time.
+ */
+ public static Uri buildProgramsUriForChannel(
+ @Nullable String inputPackage,
+ @Nullable String internalProviderId,
+ long startTime,
+ long endTime) {
+ return buildProgramsUriForChannel(inputPackage, internalProviderId)
+ .buildUpon()
+ .appendQueryParameter(TvContractCompat.PARAM_START_TIME, String.valueOf(startTime))
+ .appendQueryParameter(TvContractCompat.PARAM_END_TIME, String.valueOf(endTime))
+ .build();
+ }
+
+ /**
+ * Builds a URI that points to programs whose schedules overlap with the given time frame.
+ *
+ * @param startTime The start time used to filter programs. The returned programs should have
+ * {@link TvContractCompat.Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than this
+ * time.
+ * @param endTime The end time used to filter programs. The returned programs should have {@link
+ * TvContractCompat.Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than this time.
+ */
+ public static Uri buildProgramsUri(long startTime, long endTime) {
+ return TvContractCompat.Programs.CONTENT_URI
+ .buildUpon()
+ .appendQueryParameter(TvContractCompat.PARAM_START_TIME, String.valueOf(startTime))
+ .appendQueryParameter(TvContractCompat.PARAM_END_TIME, String.valueOf(endTime))
+ .build();
+ }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java
index 3c76c26..2a6ceec 100644
--- a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java
+++ b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java
@@ -19,11 +19,11 @@
import static android.content.Context.ACCESSIBILITY_SERVICE;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.VerticalGridView;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidedActionsStylist;
+import androidx.leanback.widget.VerticalGridView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -53,9 +53,9 @@
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
Bundle arguments = getArguments();
- view.findViewById(android.support.v17.leanback.R.id.action_fragment_root)
+ view.findViewById(androidx.leanback.R.id.action_fragment_root)
.setPadding(0, 0, 0, 0);
- mContentFragment = view.findViewById(android.support.v17.leanback.R.id.content_fragment);
+ mContentFragment = view.findViewById(androidx.leanback.R.id.content_fragment);
LinearLayout.LayoutParams guidanceLayoutParams =
(LinearLayout.LayoutParams) mContentFragment.getLayoutParams();
guidanceLayoutParams.weight = 0;
@@ -69,7 +69,7 @@
getResources()
.getDimensionPixelOffset(R.dimen.setup_done_button_container_width);
// Guided actions list
- View list = view.findViewById(android.support.v17.leanback.R.id.guidedactions_list);
+ View list = view.findViewById(androidx.leanback.R.id.guidedactions_list);
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) list.getLayoutParams();
// Use content view to check layout direction while view is being created.
if (getResources().getConfiguration().getLayoutDirection()
@@ -93,12 +93,12 @@
gridView.setWindowAlignmentOffset(offset);
gridView.setWindowAlignmentOffsetPercent(0);
gridView.setItemAlignmentOffsetPercent(0);
- ((ViewGroup) view.findViewById(android.support.v17.leanback.R.id.guidedactions_list))
+ ((ViewGroup) view.findViewById(androidx.leanback.R.id.guidedactions_list))
.setTransitionGroup(false);
// Needed for the shared element transition.
// content_frame is defined in leanback.
ViewGroup group =
- (ViewGroup) view.findViewById(android.support.v17.leanback.R.id.content_frame);
+ (ViewGroup) view.findViewById(androidx.leanback.R.id.content_frame);
group.setClipChildren(false);
group.setClipToPadding(false);
return view;
diff --git a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java
index c02d3f5..ee00e9f 100644
--- a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java
+++ b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java
@@ -112,15 +112,15 @@
@Override
protected int[] getParentIdsForDelay() {
return new int[] {
- android.support.v17.leanback.R.id.content_fragment,
- android.support.v17.leanback.R.id.guidedactions_list
+ androidx.leanback.R.id.content_fragment,
+ androidx.leanback.R.id.guidedactions_list
};
}
@Override
public int[] getSharedElementIds() {
return new int[] {
- android.support.v17.leanback.R.id.action_fragment_background, R.id.done_button_container
+ androidx.leanback.R.id.action_fragment_background, R.id.done_button_container
};
}
}
diff --git a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java
index 13b89ea..5970693 100644
--- a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java
+++ b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java
@@ -20,7 +20,7 @@
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Path;
-import android.support.v17.leanback.R;
+import androidx.leanback.R;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.view.View;
@@ -29,9 +29,9 @@
* This class is used by Slide and Explode to create an animator that goes from the start position
* to the end position. It takes into account the canceled position so that it will not blink out or
* shift suddenly when the transition is interrupted. The original class is
- * android.support.v17.leanback.transition.TranslationAnimationCreator which is hidden.
+ * androidx.leanback.transition.TranslationAnimationCreator which is hidden.
*/
-// Copied from android.support.v17.leanback.transition.TransltaionAnimationCreator
+// Copied from androidx.leanback.transition.TransltaionAnimationCreator
class TranslationAnimationCreator {
/**
* Creates an animator that can be used for x and/or y translations. When interrupted, it sets a
diff --git a/common/src/com/android/tv/common/util/CommonUtils.java b/common/src/com/android/tv/common/util/CommonUtils.java
index 4513a87..662f819 100644
--- a/common/src/com/android/tv/common/util/CommonUtils.java
+++ b/common/src/com/android/tv/common/util/CommonUtils.java
@@ -22,10 +22,8 @@
import android.os.Build;
import android.util.ArraySet;
import android.util.Log;
-import com.android.tv.common.BuildConfig;
import com.android.tv.common.CommonConstants;
import com.android.tv.common.actions.InputSetupActionUtils;
-import com.android.tv.common.experiments.Experiments;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -53,6 +51,7 @@
static {
BUNDLED_PACKAGE_SET.add("com.android.tv");
+// AOSP_Comment_Out BUNDLED_PACKAGE_SET.add(CommonConstants.BASE_PACKAGE);
}
private static Boolean sRunningInTest;
@@ -123,16 +122,11 @@
return false;
}
- /** Returns true if the application is packaged with Live TV. */
+ /** Returns true if the application is packaged with TV app. */
public static boolean isPackagedWithLiveChannels(Context context) {
return (CommonConstants.BASE_PACKAGE.equals(context.getPackageName()));
}
- /** Returns true if the current user is a developer. */
- public static boolean isDeveloper() {
- return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get();
- }
-
/** Converts time in milliseconds to a ISO 8061 string. */
public static String toIsoDateTimeString(long timeMillis) {
return ISO_8601.get().format(new Date(timeMillis));
diff --git a/common/src/com/android/tv/common/util/Debug.java b/common/src/com/android/tv/common/util/Debug.java
index ab90874..8e826ae 100644
--- a/common/src/com/android/tv/common/util/Debug.java
+++ b/common/src/com/android/tv/common/util/Debug.java
@@ -23,11 +23,11 @@
/** A class only for help developers. */
public class Debug {
/**
- * A threshold of start up time, when the start up time of Live TV is more than it, a
- * warning will show to the developer.
+ * A threshold of start up time, when the start up time of TV app is more than it, a warning
+ * will show to the developer.
*/
public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6);
- /** Tag for measuring start up time of Live TV. */
+ /** Tag for measuring start up time of TV app. */
public static final String TAG_START_UP_TIMER = "start_up_timer";
/** A global map for duration timers. */
diff --git a/common/src/com/android/tv/common/util/LocationUtils.java b/common/src/com/android/tv/common/util/LocationUtils.java
index ee5119e..9d44cf2 100644
--- a/common/src/com/android/tv/common/util/LocationUtils.java
+++ b/common/src/com/android/tv/common/util/LocationUtils.java
@@ -16,9 +16,7 @@
package com.android.tv.common.util;
-import android.Manifest;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
@@ -26,13 +24,12 @@
import android.location.LocationManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.tv.common.BuildConfig;
-
-
-
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
@@ -65,19 +62,41 @@
if (sApplicationContext == null) {
sApplicationContext = context.getApplicationContext();
}
+ /* Begin_AOSP_Comment_Out
+ if (!BuildConfig.AOSP) {
+ com.google.android.tv.livechannels.util.GoogleLocationUtilsHelper.startLocationUpdates(
+ context, LocationUtils::updateAddress);
+ return null;
+ }
+ End_AOSP_Comment_Out */
LocationUtilsHelper.startLocationUpdates();
return null;
}
+ @Nullable
+ static String getCurrentPostalCode(Context context) throws IOException {
+ Address address = getCurrentAddress(context);
+ if (address != null) {
+ Log.i(
+ TAG,
+ "Current country and postal code is "
+ + address.getCountryName()
+ + ", "
+ + address.getPostalCode());
+ return address.getPostalCode();
+ }
+ return null;
+ }
+
/** The listener used when address is updated. */
public interface OnUpdateAddressListener {
/**
* Called when address is updated.
*
- * This listener is removed when this method returns true.
+ * <p>This listener is removed when this method returns true.
*
* @return {@code true} if the job has been finished and the listener needs to be removed;
- * {@code false} otherwise.
+ * {@code false} otherwise.
*/
boolean onUpdateAddress(Address address);
}
@@ -85,8 +104,8 @@
/**
* Add an {@link OnUpdateAddressListener} instance.
*
- * Note that the listener is removed automatically when
- * {@link OnUpdateAddressListener#onUpdateAddress(Address)} is called and returns {@code true}.
+ * <p>Note that the listener is removed automatically when {@link
+ * OnUpdateAddressListener#onUpdateAddress(Address)} is called and returns {@code true}.
*/
public static void addOnUpdateAddressListener(OnUpdateAddressListener listener) {
sOnUpdateAddressListeners.add(listener);
@@ -95,8 +114,8 @@
/**
* Remove an {@link OnUpdateAddressListener} instance if it exists.
*
- * Note that the listener will be removed automatically when
- * {@link OnUpdateAddressListener#onUpdateAddress(Address)} is called and returns {@code true}.
+ * <p>Note that the listener will be removed automatically when {@link
+ * OnUpdateAddressListener#onUpdateAddress(Address)} is called and returns {@code true}.
*/
public static void removeOnUpdateAddressListener(OnUpdateAddressListener listener) {
sOnUpdateAddressListeners.remove(listener);
@@ -108,6 +127,13 @@
if (sCountry != null) {
return sCountry;
}
+ /* Begin_AOSP_Comment_Out
+ if (!BuildConfig.AOSP) {
+ sCountry =
+ com.google.android.tv.livechannels.util.GoogleLocationUtilsHelper
+ .getDeviceCountry(context);
+ }
+ End_AOSP_Comment_Out */
if (TextUtils.isEmpty(sCountry)) {
sCountry = context.getResources().getConfiguration().locale.getCountry();
}
diff --git a/common/src/com/android/tv/common/util/NetworkTrafficTags.java b/common/src/com/android/tv/common/util/NetworkTrafficTags.java
index 3c94aed..51b6c4d 100644
--- a/common/src/com/android/tv/common/util/NetworkTrafficTags.java
+++ b/common/src/com/android/tv/common/util/NetworkTrafficTags.java
@@ -20,7 +20,7 @@
import android.support.annotation.NonNull;
import java.util.concurrent.Executor;
-/** Constants for tagging network traffic in the Live channels app. */
+/** Constants for tagging network traffic in the TV app. */
public final class NetworkTrafficTags {
public static final int DEFAULT_LIVE_CHANNELS = 1;
@@ -43,16 +43,16 @@
@Override
public void execute(final @NonNull Runnable command) {
- // TODO(b/62038127): robolectric does not support lamdas in unbundled apps
- delegateExecutor.execute(
- () -> {
- TrafficStats.setThreadStatsTag(tag);
- try {
- command.run();
- } finally {
- TrafficStats.clearThreadStatsTag();
- }
- });
+ // TODO(b/62038127): robolectric does not support lamdas in unbundled apps
+ delegateExecutor.execute(
+ () -> {
+ TrafficStats.setThreadStatsTag(tag);
+ try {
+ command.run();
+ } finally {
+ TrafficStats.clearThreadStatsTag();
+ }
+ });
}
}
diff --git a/common/src/com/android/tv/common/util/PermissionUtils.java b/common/src/com/android/tv/common/util/PermissionUtils.java
index ca1abdc..e241b91 100644
--- a/common/src/com/android/tv/common/util/PermissionUtils.java
+++ b/common/src/com/android/tv/common/util/PermissionUtils.java
@@ -26,6 +26,9 @@
private static Boolean sHasAccessAllEpgPermission;
private static Boolean sHasAccessWatchedHistoryPermission;
private static Boolean sHasModifyParentalControlsPermission;
+ private static Boolean sHasChangeHdmiCecActiveSource;
+ private static Boolean sHasReadContentRatingSystem;
+
public static boolean hasAccessAllEpg(Context context) {
if (sHasAccessAllEpgPermission == null) {
@@ -70,4 +73,24 @@
return context.checkSelfPermission("android.permission.WRITE_EXTERNAL_STORAGE")
== PackageManager.PERMISSION_GRANTED;
}
+
+ public static boolean hasChangeHdmiCecActiveSource(Context context) {
+ if (sHasChangeHdmiCecActiveSource == null) {
+ sHasChangeHdmiCecActiveSource =
+ context.checkSelfPermission(
+ "android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE")
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return sHasChangeHdmiCecActiveSource;
+ }
+
+ public static boolean hasReadContetnRatingSystem(Context context) {
+ if (sHasReadContentRatingSystem == null) {
+ sHasReadContentRatingSystem =
+ context.checkSelfPermission(
+ "android.permission.READ_CONTENT_RATING_SYSTEMS")
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return sHasReadContentRatingSystem;
+ }
}
diff --git a/common/src/com/android/tv/common/util/PostalCodeUtils.java b/common/src/com/android/tv/common/util/PostalCodeUtils.java
index c0917af..6ca3d48 100644
--- a/common/src/com/android/tv/common/util/PostalCodeUtils.java
+++ b/common/src/com/android/tv/common/util/PostalCodeUtils.java
@@ -17,12 +17,12 @@
package com.android.tv.common.util;
import android.content.Context;
-import android.location.Address;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.tv.common.CommonPreferences;
+
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
@@ -62,7 +62,7 @@
/** Returns {@code true} if postal code has been changed */
public static boolean updatePostalCode(Context context)
throws IOException, SecurityException, NoPostalCodeException {
- String postalCode = getPostalCode(context);
+ String postalCode = LocationUtils.getCurrentPostalCode(context);
String lastPostalCode = getLastPostalCode(context);
if (TextUtils.isEmpty(postalCode)) {
if (TextUtils.isEmpty(lastPostalCode)) {
@@ -92,21 +92,6 @@
CommonPreferences.setLastPostalCode(context, postalCode);
}
- @Nullable
- private static String getPostalCode(Context context) throws IOException, SecurityException {
- Address address = LocationUtils.getCurrentAddress(context);
- if (address != null) {
- Log.i(
- TAG,
- "Current country and postal code is "
- + address.getCountryName()
- + ", "
- + address.getPostalCode());
- return address.getPostalCode();
- }
- return null;
- }
-
/** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */
public static class NoPostalCodeException extends Exception {
public NoPostalCodeException() {}
diff --git a/common/src/com/android/tv/common/util/SystemProperties.java b/common/src/com/android/tv/common/util/SystemProperties.java
index 6ac2907..72920b6 100644
--- a/common/src/com/android/tv/common/util/SystemProperties.java
+++ b/common/src/com/android/tv/common/util/SystemProperties.java
@@ -21,25 +21,6 @@
/** A convenience class for getting TV related system properties. */
public final class SystemProperties {
- /** Allow Google Analytics for eng builds. */
- public static final BooleanSystemProperty ALLOW_ANALYTICS_IN_ENG =
- new BooleanSystemProperty("tv_allow_analytics_in_eng", false);
-
- /** Allow Strict mode for debug builds. */
- public static final BooleanSystemProperty ALLOW_STRICT_MODE =
- new BooleanSystemProperty("tv_allow_strict_mode", true);
-
- /** When true {@link android.view.KeyEvent}s are logged. Defaults to false. */
- public static final BooleanSystemProperty LOG_KEYEVENT =
- new BooleanSystemProperty("tv_log_keyevent", false);
- /** When true debug keys are used. Defaults to false. */
- public static final BooleanSystemProperty USE_DEBUG_KEYS =
- new BooleanSystemProperty("tv_use_debug_keys", false);
-
- /** Send {@link com.android.tv.analytics.Tracker} information. Defaults to {@code true}. */
- public static final BooleanSystemProperty USE_TRACKER =
- new BooleanSystemProperty("tv_use_tracker", true);
-
/** Allow third party inputs. */
public static final BooleanSystemProperty ALLOW_THIRD_PARTY_INPUTS =
new BooleanSystemProperty("ro.tv_allow_third_party_inputs", true);
diff --git a/src/com/android/tv/util/SqlParams.java b/common/src/com/android/tv/common/util/sql/SqlParams.java
similarity index 97%
rename from src/com/android/tv/util/SqlParams.java
rename to common/src/com/android/tv/common/util/sql/SqlParams.java
index fa557ba..87fcabd 100644
--- a/src/com/android/tv/util/SqlParams.java
+++ b/common/src/com/android/tv/common/util/sql/SqlParams.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.util;
+package com.android.tv.common.util.sql;
import android.database.DatabaseUtils;
import android.support.annotation.Nullable;
diff --git a/common/tests/robotests/src/com/android/tv/common/TvContentRatingCacheTest.java b/common/tests/robotests/src/com/android/tv/common/TvContentRatingCacheTest.java
new file mode 100644
index 0000000..01bfdc4
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/TvContentRatingCacheTest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentCallbacks2;
+import android.media.tv.TvContentRating;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.constants.TvContentRatingConstants;
+import com.google.common.collect.ImmutableList;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link TvContentRatingCache}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class TvContentRatingCacheTest {
+ /** US_TV_MA and US_TV_Y7 in order */
+ public static final String MA_AND_Y7 =
+ TvContentRatingConstants.STRING_US_TV_MA
+ + ","
+ + TvContentRatingConstants.STRING_US_TV_Y7_US_TV_FV;
+
+ /** US_TV_MA and US_TV_Y7 not in order */
+ public static final String Y7_AND_MA =
+ TvContentRatingConstants.STRING_US_TV_Y7_US_TV_FV
+ + ","
+ + TvContentRatingConstants.STRING_US_TV_MA;
+
+ final TvContentRatingCache mCache = TvContentRatingCache.getInstance();
+
+ @Before
+ public void setUp() {
+ mCache.performTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ }
+
+ @After
+ public void tearDown() {
+ mCache.performTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ }
+
+ @Test
+ public void testGetRatings_US_TV_MA() {
+ ImmutableList<TvContentRating> result =
+ mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA);
+ assertThat(result).contains(TvContentRatingConstants.CONTENT_RATING_US_TV_MA);
+ }
+
+ @Test
+ public void testGetRatings_US_TV_MA_same() {
+ ImmutableList<TvContentRating> first =
+ mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA);
+ ImmutableList<TvContentRating> second =
+ mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA);
+ assertThat(first).isSameInstanceAs(second);
+ }
+
+ @Test
+ public void testGetRatings_US_TV_MA_diffAfterClear() {
+ ImmutableList<TvContentRating> first =
+ mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA);
+ mCache.performTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ ImmutableList<TvContentRating> second =
+ mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA);
+ assertThat(first).isNotSameInstanceAs(second);
+ }
+
+ @Test
+ public void testGetRatings_TWO_orderDoesNotMatter() {
+ ImmutableList<TvContentRating> first = mCache.getRatings(MA_AND_Y7);
+ ImmutableList<TvContentRating> second = mCache.getRatings(Y7_AND_MA);
+ assertThat(first).isSameInstanceAs(second);
+ }
+
+ @Test
+ public void testContentRatingsToString_null() {
+ String result = TvContentRatingCache.contentRatingsToString(null);
+ assertWithMessage("ratings string").that(result).isNull();
+ }
+
+ @Test
+ public void testContentRatingsToString_none() {
+ String result = TvContentRatingCache.contentRatingsToString(ImmutableList.of());
+ assertWithMessage("ratings string").that(result).isEmpty();
+ }
+
+ @Test
+ public void testContentRatingsToString_one() {
+ String result =
+ TvContentRatingCache.contentRatingsToString(
+ ImmutableList.of(TvContentRatingConstants.CONTENT_RATING_US_TV_MA));
+ assertWithMessage("ratings string")
+ .that(result)
+ .isEqualTo(TvContentRatingConstants.STRING_US_TV_MA);
+ }
+
+ @Test
+ public void testContentRatingsToString_twoInOrder() {
+ String result =
+ TvContentRatingCache.contentRatingsToString(
+ ImmutableList.of(
+ TvContentRatingConstants.CONTENT_RATING_US_TV_MA,
+ TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV));
+ assertWithMessage("ratings string").that(result).isEqualTo(MA_AND_Y7);
+ }
+
+ @Test
+ public void testContentRatingsToString_twoNotInOrder() {
+ String result =
+ TvContentRatingCache.contentRatingsToString(
+ ImmutableList.of(
+ TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV,
+ TvContentRatingConstants.CONTENT_RATING_US_TV_MA));
+ assertWithMessage("ratings string").that(result).isEqualTo(MA_AND_Y7);
+ }
+
+ @Test
+ public void testContentRatingsToString_double() {
+ String result =
+ TvContentRatingCache.contentRatingsToString(
+ ImmutableList.of(
+ TvContentRatingConstants.CONTENT_RATING_US_TV_MA,
+ TvContentRatingConstants.CONTENT_RATING_US_TV_MA));
+ assertWithMessage("ratings string")
+ .that(result)
+ .isEqualTo(TvContentRatingConstants.STRING_US_TV_MA);
+ }
+
+ @Test
+ public void testStringToContentRatings_null() {
+ assertThat(TvContentRatingCache.stringToContentRatings(null)).isEmpty();
+ }
+
+ @Test
+ public void testStringToContentRatings_none() {
+ assertThat(TvContentRatingCache.stringToContentRatings("")).isEmpty();
+ }
+
+ @Test
+ public void testStringToContentRatings_bad() {
+ assertThat(TvContentRatingCache.stringToContentRatings("bad")).isEmpty();
+ }
+
+ @Test
+ public void testStringToContentRatings_oneGoodOneBad() {
+ ImmutableList<TvContentRating> results =
+ TvContentRatingCache.stringToContentRatings(
+ TvContentRatingConstants.STRING_US_TV_Y7_US_TV_FV + ",bad");
+ assertWithMessage("ratings")
+ .that(results)
+ .containsExactly(TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV);
+ }
+
+ @Test
+ public void testStringToContentRatings_one() {
+ ImmutableList<TvContentRating> results =
+ TvContentRatingCache.stringToContentRatings(
+ TvContentRatingConstants.STRING_US_TV_Y7_US_TV_FV);
+ assertWithMessage("ratings")
+ .that(results)
+ .containsExactly(TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV);
+ }
+
+ @Test
+ public void testStringToContentRatings_twoNotInOrder() {
+ ImmutableList<TvContentRating> results =
+ TvContentRatingCache.stringToContentRatings(Y7_AND_MA);
+ assertWithMessage("ratings")
+ .that(results)
+ .containsExactly(
+ TvContentRatingConstants.CONTENT_RATING_US_TV_MA,
+ TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV);
+ }
+
+ @Test
+ public void testStringToContentRatings_twoInOrder() {
+ ImmutableList<TvContentRating> results =
+ TvContentRatingCache.stringToContentRatings(MA_AND_Y7);
+ assertWithMessage("ratings")
+ .that(results)
+ .containsExactly(
+ TvContentRatingConstants.CONTENT_RATING_US_TV_MA,
+ TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV);
+ }
+
+ @Test
+ public void testStringToContentRatings_double() {
+ ImmutableList<TvContentRating> results =
+ TvContentRatingCache.stringToContentRatings(
+ TvContentRatingConstants.STRING_US_TV_MA
+ + ","
+ + TvContentRatingConstants.STRING_US_TV_MA);
+ assertWithMessage("ratings")
+ .that(results)
+ .containsExactly((TvContentRatingConstants.CONTENT_RATING_US_TV_MA));
+
+ assertThat(results).containsExactly(TvContentRatingConstants.CONTENT_RATING_US_TV_MA);
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/actions/InputSetupActionUtilsTest.java b/common/tests/robotests/src/com/android/tv/common/actions/InputSetupActionUtilsTest.java
new file mode 100644
index 0000000..17d400a
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/actions/InputSetupActionUtilsTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2018 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.common.actions;
+
+import static com.google.android.libraries.testing.truth.BundleSubject.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.os.Bundle;
+import com.android.tv.testing.constants.ConfigConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link InputSetupActionUtils} */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class InputSetupActionUtilsTest {
+
+ @Test
+ public void hasInputSetupAction_launchInputSetup() {
+ Intent intent = new Intent("com.android.tv.action.LAUNCH_INPUT_SETUP");
+ assertThat(InputSetupActionUtils.hasInputSetupAction(intent)).isTrue();
+ }
+
+ @Test
+ public void hasInputSetupAction_googleLaunchInputSetup() {
+ Intent intent = new Intent("com.google.android.tv.action.LAUNCH_INPUT_SETUP");
+ assertThat(InputSetupActionUtils.hasInputSetupAction(intent)).isTrue();
+ }
+
+ @Test
+ public void hasInputSetupAction_bad() {
+ Intent intent = new Intent("com.example.action.LAUNCH_INPUT_SETUP");
+ assertThat(InputSetupActionUtils.hasInputSetupAction(intent)).isFalse();
+ }
+
+ @Test
+ public void getExtraActivityAfter_null() {
+ Intent intent = new Intent();
+ assertThat(InputSetupActionUtils.getExtraActivityAfter(intent)).isNull();
+ }
+
+ @Test
+ public void getExtraActivityAfter_activityAfter() {
+ Intent intent = new Intent();
+ Intent after = new Intent("after");
+ intent.putExtra("com.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION", after);
+ assertThat(InputSetupActionUtils.getExtraActivityAfter(intent)).isEqualTo(after);
+ }
+
+ @Test
+ public void getExtraActivityAfter_googleActivityAfter() {
+ Intent intent = new Intent();
+ Intent after = new Intent("google_setup");
+ intent.putExtra("com.google.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION", after);
+ assertThat(InputSetupActionUtils.getExtraActivityAfter(intent)).isEqualTo(after);
+ }
+
+ @Test
+ public void getExtraSetupIntent_null() {
+ Intent intent = new Intent();
+ assertThat(InputSetupActionUtils.getExtraSetupIntent(intent)).isNull();
+ }
+
+ @Test
+ public void getExtraSetupIntent_setupIntent() {
+ Intent intent = new Intent();
+ Intent setup = new Intent("setup");
+ intent.putExtra("com.android.tv.extra.SETUP_INTENT", setup);
+ assertThat(InputSetupActionUtils.getExtraSetupIntent(intent)).isEqualTo(setup);
+ }
+
+ @Test
+ public void getExtraSetupIntent_googleSetupIntent() {
+ Intent intent = new Intent();
+ Intent setup = new Intent("google_setup");
+ intent.putExtra("com.google.android.tv.extra.SETUP_INTENT", setup);
+ assertThat(InputSetupActionUtils.getExtraSetupIntent(intent)).isEqualTo(setup);
+ }
+
+ @Test
+ public void removeSetupIntent_empty() {
+ Bundle extras = new Bundle();
+ InputSetupActionUtils.removeSetupIntent(extras);
+ assertThat(extras).exactlyMatches(new Bundle());
+ }
+
+ @Test
+ public void removeSetupIntent_other() {
+ Bundle extras = createTestBundle();
+ Bundle expected = createTestBundle();
+ InputSetupActionUtils.removeSetupIntent(extras);
+ assertThat(extras).exactlyMatches(expected);
+ }
+
+ @Test
+ public void removeSetupIntent_setup() {
+ Bundle extras = createTestBundle();
+ Bundle expected = createTestBundle();
+ Intent setup = new Intent("setup");
+ extras.putParcelable("com.android.tv.extra.SETUP_INTENT", setup);
+ InputSetupActionUtils.removeSetupIntent(extras);
+ assertThat(extras).exactlyMatches(expected);
+ }
+
+ @Test
+ public void removeSetupIntent_googleSetup() {
+ Bundle extras = createTestBundle();
+ Bundle expected = createTestBundle();
+ Intent googleSetup = new Intent("googleSetup");
+ extras.putParcelable("com.google.android.tv.extra.SETUP_INTENT", googleSetup);
+ InputSetupActionUtils.removeSetupIntent(extras);
+ assertThat(extras).exactlyMatches(expected);
+ }
+
+ @Test
+ public void removeSetupIntent_bothSetups() {
+ Bundle extras = createTestBundle();
+ Bundle expected = createTestBundle();
+ Intent setup = new Intent("setup");
+ extras.putParcelable("com.android.tv.extra.SETUP_INTENT", setup);
+ Intent googleSetup = new Intent("googleSetup");
+ extras.putParcelable("com.google.android.tv.extra.SETUP_INTENT", googleSetup);
+ InputSetupActionUtils.removeSetupIntent(extras);
+ assertThat(extras).exactlyMatches(expected);
+ }
+
+ private static Bundle createTestBundle() {
+ Bundle extras = new Bundle();
+ extras.putInt("other", 1);
+ return extras;
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/compat/TvInputInfoCompatTest.java b/common/tests/robotests/src/com/android/tv/common/compat/TvInputInfoCompatTest.java
new file mode 100644
index 0000000..93c04b9
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/compat/TvInputInfoCompatTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 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.common.compat;
+
+import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.fail;
+
+import android.content.pm.ResolveInfo;
+import android.content.res.XmlResourceParser;
+import android.media.tv.TvInputInfo;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.utils.TestUtils;
+
+import java.io.StringReader;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+/**
+ * Tests for {@link TvInputInfoCompat}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class TvInputInfoCompatTest {
+ private TvInputInfoCompat mTvInputInfoCompat;
+ private String mInputXml;
+ @Before
+ public void setUp() throws Exception {
+ ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test");
+ TvInputInfo info =
+ TestUtils.createTvInputInfo(
+ resolveInfo, "test_input", "test1", TvInputInfo.TYPE_OTHER, false);
+ mTvInputInfoCompat =
+ new TvInputInfoCompat(
+ InstrumentationRegistry.getTargetContext(), info) {
+ @Override
+ XmlPullParser getXmlResourceParser() {
+ XmlPullParser xpp = null;
+ try {
+ xpp = XmlPullParserFactory.newInstance().newPullParser();
+ xpp.setInput(new StringReader(mInputXml));
+ xpp.setFeature(
+ XmlResourceParser.FEATURE_PROCESS_NAMESPACES, true);
+ } catch (XmlPullParserException e) {
+ fail("failed in setUp() " + e.getMessage());
+ }
+ return xpp;
+ }
+ };
+ }
+
+ @Test
+ public void testGetAttributeValue_notTvInputTag() {
+ mInputXml =
+ "<not-tv-input xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:setupActivity=\"\"\n"
+ + " android:settingsActivity=\"\"/>\n";
+ assertThat(mTvInputInfoCompat.getExtras()).isEmpty();
+ }
+
+ @Test
+ public void testGetAttributeValue_noExtra() {
+ mInputXml =
+ "<not-tv-input xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:setupActivity=\"\"\n"
+ + " android:settingsActivity=\"\"/>\n";
+ assertThat(mTvInputInfoCompat.getExtras()).isEmpty();
+ }
+
+ @Test
+ public void testGetAttributeValue() {
+ mInputXml =
+ "<tv-input xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:setupActivity=\"\"\n"
+ + " android:settingsActivity=\"\">\n"
+ + " <extra android:name=\"otherAttr1\" android:value=\"false\" />\n"
+ + " <extra android:name=\"otherAttr2\" android:value=\"false\" />\n"
+ + " <extra android:name="
+ + " \"com.android.tv.common.compat.tvinputinfocompat.audioOnly\""
+ + " android:value=\"true\" />\n"
+ + "</tv-input>";
+ assertThat(mTvInputInfoCompat.getExtras())
+ .containsExactly("otherAttr1", "false", "otherAttr2", "false",
+ "com.android.tv.common.compat.tvinputinfocompat.audioOnly", "true");
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/compat/internal/PrivateCommandTest.java b/common/tests/robotests/src/com/android/tv/common/compat/internal/PrivateCommandTest.java
new file mode 100644
index 0000000..02705a4
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/compat/internal/PrivateCommandTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 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.common.compat.internal;
+
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+
+import com.android.tv.common.compat.api.SessionCompatCommands;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.testing.mockito.Mocks;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests sending {@link Commands.PrivateCommand}s to a {@link SessionCompatCommands} from {@link
+ * TvViewCompatProcessor} via {@link TifSessionCompatProcessor}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class PrivateCommandTest {
+ @Rule public final Mocks mocks = new Mocks(this);
+
+ @Mock SessionCompatCommands mCallback;
+
+ private TvViewCompatProcessor mTvViewCompatProcessor;
+
+ @Before
+ public void setUp() {
+ TifSessionCompatProcessor sessionCompatProcessor =
+ new TifSessionCompatProcessor(null, mCallback);
+ mTvViewCompatProcessor =
+ new TvViewCompatProcessor(sessionCompatProcessor::handleAppPrivateCommand);
+ }
+
+ @Test
+ public void notifyDevToast() throws InvalidProtocolBufferException {
+ mTvViewCompatProcessor.devMessage("Hello Developers");
+ verify(mCallback, only()).onDevMessage("Hello Developers");
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/compat/internal/PrivateRecordingCommandTest.java b/common/tests/robotests/src/com/android/tv/common/compat/internal/PrivateRecordingCommandTest.java
new file mode 100644
index 0000000..6d9949f
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/compat/internal/PrivateRecordingCommandTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 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.common.compat.internal;
+
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+
+import com.android.tv.common.compat.api.RecordingSessionCompatCommands;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.testing.mockito.Mocks;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests sending {@link RecordingCommands.PrivateRecordingCommand}s to a {@link
+ * RecordingSessionCompatCommands} from {@link RecordingClientCompatProcessor} via {@link
+ * RecordingSessionCompatProcessor}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class PrivateRecordingCommandTest {
+ @Rule public final Mocks mocks = new Mocks(this);
+
+ @Mock private RecordingSessionCompatCommands mCallback;
+
+ private RecordingClientCompatProcessor mCompatProcessor;
+
+ @Before
+ public void setUp() {
+ RecordingSessionCompatProcessor sessionCompatProcessor =
+ new RecordingSessionCompatProcessor(null, mCallback);
+ mCompatProcessor =
+ new RecordingClientCompatProcessor(
+ sessionCompatProcessor::handleAppPrivateCommand, null);
+ }
+
+ @Test
+ public void notifyDevToast() throws InvalidProtocolBufferException {
+ mCompatProcessor.devMessage("Hello Recorders");
+ verify(mCallback, only()).onDevMessage("Hello Recorders");
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/compat/internal/RecordingSessionEventTest.java b/common/tests/robotests/src/com/android/tv/common/compat/internal/RecordingSessionEventTest.java
new file mode 100644
index 0000000..b5bd0a0
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/compat/internal/RecordingSessionEventTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 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.common.compat.internal;
+
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+
+import com.android.tv.common.compat.api.RecordingClientCallbackCompatEvents;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.testing.mockito.Mocks;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests sending {@link RecordingEvents.RecordingSessionEvent}s to a {@link
+ * RecordingClientCallbackCompatEvents} from {@link RecordingSessionCompatProcessor} via {@link
+ * RecordingClientCompatProcessor}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class RecordingSessionEventTest {
+ @Rule public final Mocks mocks = new Mocks(this);
+
+ @Mock RecordingClientCallbackCompatEvents mCallback;
+
+ private RecordingSessionCompatProcessor mCompatProcess;
+
+ @Before
+ public void setUp() {
+ RecordingClientCompatProcessor compatProcessor =
+ new RecordingClientCompatProcessor(null, mCallback);
+ mCompatProcess =
+ new RecordingSessionCompatProcessor(
+ (event, data) -> compatProcessor.handleEvent("testinput", event, data),
+ null);
+ }
+
+ @Test
+ public void notifyDevToast() throws InvalidProtocolBufferException {
+ mCompatProcess.notifyDevToast("Recording");
+ verify(mCallback, only()).onDevToast("testinput", "Recording");
+ }
+
+ @Test
+ public void notifyRecordingStarted() throws InvalidProtocolBufferException {
+ mCompatProcess.notifyRecordingStarted("file:example");
+ verify(mCallback, only()).onRecordingStarted("testinput", "file:example");
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/compat/internal/SessionEventTest.java b/common/tests/robotests/src/com/android/tv/common/compat/internal/SessionEventTest.java
new file mode 100644
index 0000000..0cc300b
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/compat/internal/SessionEventTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 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.common.compat.internal;
+
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+
+import com.android.tv.common.compat.api.TvInputCallbackCompatEvents;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.testing.mockito.Mocks;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests sending {@link Events.SessionEvent}s to a {@link TvInputCallbackCompatEvents} from {@link
+ * TifSessionCompatProcessor} via {@link TvViewCompatProcessor}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class SessionEventTest {
+ @Rule public final Mocks mocks = new Mocks(this);
+
+ @Mock TvInputCallbackCompatEvents mCallback;
+
+ private TifSessionCompatProcessor mCompatProcess;
+
+ @Before
+ public void setUp() {
+ TvViewCompatProcessor tvViewCompatProcessor = new TvViewCompatProcessor(null);
+ tvViewCompatProcessor.setCallback(mCallback);
+ mCompatProcess =
+ new TifSessionCompatProcessor(
+ (event, data) ->
+ tvViewCompatProcessor.handleEvent("testinput", event, data),
+ null);
+ }
+
+ @Test
+ public void notifyDevToast() throws InvalidProtocolBufferException {
+ mCompatProcess.notifyDevToast("testing");
+ verify(mCallback, only()).onDevToast("testinput", "testing");
+ }
+
+ @Test
+ public void notifySignalStrength() throws InvalidProtocolBufferException {
+ mCompatProcess.notifySignalStrength(3);
+ verify(mCallback, only()).onSignalStrength("testinput", 3);
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/dev/DeveloperPreferenceTest.java b/common/tests/robotests/src/com/android/tv/common/dev/DeveloperPreferenceTest.java
new file mode 100644
index 0000000..a9c15ad
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/dev/DeveloperPreferenceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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.common.dev;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.testing.constants.ConfigConstants;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link DeveloperPreference}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class DeveloperPreferenceTest {
+
+ @Test
+ public void createBoolean_default_true() {
+ DeveloperPreference<Boolean> devPref = DeveloperPreference.create("test", true);
+ assertThat(devPref.get(RuntimeEnvironment.systemContext)).isTrue();
+ DeveloperPreference.getPreferences(RuntimeEnvironment.systemContext)
+ .edit()
+ .putBoolean("test", false)
+ .apply();
+ assertThat(devPref.get(RuntimeEnvironment.systemContext)).isFalse();
+ }
+
+ @Test
+ public void create_integer_default_one() {
+ DeveloperPreference<Integer> devPref = DeveloperPreference.create("test", 1);
+ assertThat(devPref.get(RuntimeEnvironment.systemContext)).isEqualTo(1);
+ DeveloperPreference.getPreferences(RuntimeEnvironment.systemContext)
+ .edit()
+ .putInt("test", 2)
+ .apply();
+ assertThat(devPref.get(RuntimeEnvironment.systemContext)).isEqualTo(2);
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/support/tis/BaseTvInputServiceTest.java b/common/tests/robotests/src/com/android/tv/common/support/tis/BaseTvInputServiceTest.java
new file mode 100644
index 0000000..7deef75
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/support/tis/BaseTvInputServiceTest.java
@@ -0,0 +1,145 @@
+package com.android.tv.common.support.tis;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.view.Surface;
+import com.android.tv.common.support.tis.TifSession.TifSessionCallbacks;
+import com.android.tv.common.support.tis.TifSession.TifSessionFactory;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ServiceController;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link BaseTvInputService}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP, maxSdk = Build.VERSION_CODES.P)
+public class BaseTvInputServiceTest {
+
+ private static class TestTvInputService extends BaseTvInputService {
+
+ private final SessionManager sessionManager = new SimpleSessionManager(1);
+
+ private int parentalControlsChangedCount = 0;
+ private final TifSessionFactory sessionFactory;
+
+ private TestTvInputService() {
+ super();
+ this.sessionFactory =
+ new TifSessionFactory() {
+ @Override
+ public TifSession create(TifSessionCallbacks callbacks, String inputId) {
+ return new TifSession(callbacks) {
+ @Override
+ public boolean onSetSurface(@Nullable Surface surface) {
+ return false;
+ }
+
+ @Override
+ public void onSurfaceChanged(int format, int width, int height) {}
+
+ @Override
+ public void onSetStreamVolume(float volume) {}
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ return false;
+ }
+
+ @Override
+ public void onSetCaptionEnabled(boolean enabled) {}
+
+ @Override
+ public void onUnblockContent(TvContentRating unblockedRating) {}
+
+ @Override
+ public void onParentalControlsChanged() {
+ parentalControlsChangedCount++;
+ }
+ };
+ }
+ };
+ }
+
+ @Override
+ protected TifSessionFactory getTifSessionFactory() {
+ return sessionFactory;
+ }
+
+ @Override
+ protected SessionManager getSessionManager() {
+ return sessionManager;
+ }
+
+ private int getParentalControlsChangedCount() {
+ return parentalControlsChangedCount;
+ }
+ }
+
+ TestTvInputService tvInputService;
+ ServiceController<TestTvInputService> controller;
+
+ @Before
+ public void setUp() {
+ controller = Robolectric.buildService(TestTvInputService.class);
+ tvInputService = controller.create().get();
+ }
+
+ @Test
+ public void createSession_once() {
+ assertThat(tvInputService.onCreateSession("test")).isNotNull();
+ }
+
+ @Test
+ public void createSession_twice() {
+ WrappedSession first = tvInputService.onCreateSession("test");
+ assertThat(first).isNotNull();
+ WrappedSession second = tvInputService.onCreateSession("test");
+ assertThat(second).isNull();
+ }
+
+ @Test
+ public void createSession_release() {
+ WrappedSession first = tvInputService.onCreateSession("test");
+ assertThat(first).isNotNull();
+ first.onRelease();
+ WrappedSession second = tvInputService.onCreateSession("test");
+ assertThat(second).isNotNull();
+ assertThat(second).isNotSameInstanceAs(first);
+ }
+
+ @Test
+ public void testReceiver_actionEnabledChanged() {
+ tvInputService.getSessionManager().addSession(tvInputService.onCreateSession("test"));
+ tvInputService.broadcastReceiver.onReceive(
+ RuntimeEnvironment.application,
+ new Intent(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED));
+ assertThat(tvInputService.getParentalControlsChangedCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testReceiver_actionBlockedChanged() {
+ tvInputService.getSessionManager().addSession(tvInputService.onCreateSession("test"));
+ tvInputService.broadcastReceiver.onReceive(
+ RuntimeEnvironment.application,
+ new Intent(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED));
+ assertThat(tvInputService.getParentalControlsChangedCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testReceiver_invalidAction() {
+ tvInputService.getSessionManager().addSession(tvInputService.onCreateSession("test"));
+ tvInputService.broadcastReceiver.onReceive(
+ RuntimeEnvironment.application, new Intent("test"));
+ assertThat(tvInputService.getParentalControlsChangedCount()).isEqualTo(0);
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/support/tis/SimpleSessionManagerTest.java b/common/tests/robotests/src/com/android/tv/common/support/tis/SimpleSessionManagerTest.java
new file mode 100644
index 0000000..71ff199
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/support/tis/SimpleSessionManagerTest.java
@@ -0,0 +1,122 @@
+package com.android.tv.common.support.tis;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.tv.TvContentRating;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.FloatRange;
+import android.support.annotation.Nullable;
+import android.view.Surface;
+import com.android.tv.common.support.tis.TifSession.TifSessionCallbacks;
+import com.android.tv.common.support.tis.TifSession.TifSessionFactory;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link SimpleSessionManager}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP, maxSdk = Build.VERSION_CODES.P)
+public class SimpleSessionManagerTest {
+
+ private SimpleSessionManager sessionManager;
+
+ @Before
+ public void setup() {
+ sessionManager = new SimpleSessionManager(1);
+ }
+
+ @Test
+ public void canCreateSession_none() {
+ assertThat(sessionManager.canCreateNewSession()).isTrue();
+ }
+
+ @Test
+ public void canCreateSession_one() {
+ sessionManager.addSession(createTestSession());
+ assertThat(sessionManager.canCreateNewSession()).isFalse();
+ }
+
+ @Test
+ public void addSession() {
+ assertThat(sessionManager.getSessionCount()).isEqualTo(0);
+ sessionManager.addSession(createTestSession());
+ assertThat(sessionManager.getSessionCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void onRelease() {
+ WrappedSession testSession = createTestSession();
+ sessionManager.addSession(testSession);
+ assertThat(sessionManager.getSessionCount()).isEqualTo(1);
+ testSession.onRelease();
+ assertThat(sessionManager.getSessionCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void onRelease_withUnRegisteredSession() {
+ WrappedSession testSession = createTestSession();
+ sessionManager.addSession(createTestSession());
+ assertThat(sessionManager.getSessionCount()).isEqualTo(1);
+ testSession.onRelease();
+ assertThat(sessionManager.getSessionCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void getSessions() {
+ WrappedSession testSession = createTestSession();
+ sessionManager.addSession(testSession);
+ assertThat(sessionManager.getSessions()).containsExactly(testSession);
+ }
+
+ private WrappedSession createTestSession() {
+ return new WrappedSession(
+ RuntimeEnvironment.application,
+ sessionManager,
+ new TestTifSessionFactory(),
+ "testInputId");
+ }
+
+ private static final class TestTifSessionFactory implements TifSessionFactory {
+
+ @Override
+ public TifSession create(TifSessionCallbacks callbacks, String inputId) {
+ return new TestTifSession(callbacks);
+ }
+ }
+
+ private static final class TestTifSession extends TifSession {
+
+ private TestTifSession(TifSessionCallbacks callbacks) {
+ super(callbacks);
+ }
+
+ @Override
+ public void onRelease() {}
+
+ @Override
+ public boolean onSetSurface(@Nullable Surface surface) {
+ return false;
+ }
+
+ @Override
+ public void onSurfaceChanged(int format, int width, int height) {}
+
+ @Override
+ public void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume) {}
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ return false;
+ }
+
+ @Override
+ public void onSetCaptionEnabled(boolean enabled) {}
+
+ @Override
+ public void onUnblockContent(TvContentRating unblockedRating) {}
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/support/tis/TisSessionTest.java b/common/tests/robotests/src/com/android/tv/common/support/tis/TisSessionTest.java
new file mode 100644
index 0000000..e324017
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/support/tis/TisSessionTest.java
@@ -0,0 +1,205 @@
+package com.android.tv.common.support.tis;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.FloatRange;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Pair;
+import android.view.Surface;
+import android.view.View;
+import com.android.tv.common.support.tis.TifSession.TifSessionCallbacks;
+import com.google.common.collect.ImmutableList;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link TifSession}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP, maxSdk = Build.VERSION_CODES.P)
+public class TisSessionTest {
+
+ private TestSession testSession;
+ private TestCallback testCallback;
+
+ @Before
+ public void setup() {
+ testCallback = new TestCallback();
+ testSession = new TestSession(testCallback);
+ }
+
+ @Test
+ public void notifyChannelReturned() {
+ Uri uri = Uri.parse("http://example.com");
+ testSession.notifyChannelRetuned(uri);
+ assertThat(testCallback.channelUri).isEqualTo(uri);
+ }
+
+ @Test
+ public void notifyTracksChanged() {
+ List<TvTrackInfo> tracks =
+ ImmutableList.of(new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "test").build());
+ testSession.notifyTracksChanged(tracks);
+ assertThat(testCallback.tracks).isEqualTo(tracks);
+ }
+
+ @Test
+ public void notifyTrackSelected() {
+ testSession.notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, "audio_test");
+ assertThat(testCallback.trackSelected)
+ .isEqualTo(Pair.create(TvTrackInfo.TYPE_AUDIO, "audio_test"));
+ }
+
+ @Test
+ public void notifyVideoAvailable() {
+ testSession.notifyVideoAvailable();
+ assertThat(testCallback.videoAvailable).isTrue();
+ }
+
+ @Test
+ public void notifyVideoUnavailable() {
+ testSession.notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
+ assertThat(testCallback.notifyVideoUnavailableReason)
+ .isEqualTo(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
+ }
+
+ @Test
+ public void notifyContentAllowed() {
+ testSession.notifyContentAllowed();
+ assertThat(testCallback.contentAllowed).isTrue();
+ }
+
+ @Test
+ public void notifyContentBlocked() {
+ TvContentRating rating = TvContentRating.createRating("1", "2", "3");
+ testSession.notifyContentBlocked(rating);
+ assertThat(testCallback.blockedContentRating).isEqualTo(rating);
+ }
+
+ @Test
+ public void notifyTimeShiftStatusChanged() {
+ testSession.notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE);
+ assertThat(testCallback.timeShiftStatus)
+ .isEqualTo(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE);
+ }
+
+ @Test
+ public void testSetOverlayViewEnabled() {
+ testSession.helpTestSetOverlayViewEnabled(true);
+ assertThat(testCallback.overlayViewEnabled).isTrue();
+
+ testSession.helpTestSetOverlayViewEnabled(false);
+ assertThat(testCallback.overlayViewEnabled).isFalse();
+ }
+
+ @Test
+ public void testOnCreateOverlayView() {
+ View actualView = testSession.onCreateOverlayView();
+ assertThat(actualView).isNull(); // Default implementation returns a null.
+ }
+
+ @Test
+ public void testOnOverlayViewSizeChanged() {
+ testSession.onOverlayViewSizeChanged(5 /* width */, 7 /* height */);
+ // Just verifing that the call completes.
+ }
+
+ private static final class TestCallback implements TifSessionCallbacks {
+
+ private Uri channelUri;
+ private List<TvTrackInfo> tracks;
+ private Pair<Integer, String> trackSelected;
+ private boolean videoAvailable;
+ private int notifyVideoUnavailableReason;
+ private boolean contentAllowed;
+ private TvContentRating blockedContentRating;
+ private int timeShiftStatus;
+ private boolean overlayViewEnabled;
+
+ @Override
+ public void notifyChannelRetuned(Uri channelUri) {
+ this.channelUri = channelUri;
+ }
+
+ @Override
+ public void notifyTracksChanged(List<TvTrackInfo> tracks) {
+ this.tracks = tracks;
+ }
+
+ @Override
+ public void notifyTrackSelected(int type, String trackId) {
+ this.trackSelected = Pair.create(type, trackId);
+ }
+
+ @Override
+ public void notifyVideoAvailable() {
+ this.videoAvailable = true;
+ }
+
+ @Override
+ public void notifyVideoUnavailable(int reason) {
+ this.notifyVideoUnavailableReason = reason;
+ }
+
+ @Override
+ public void notifyContentAllowed() {
+ this.contentAllowed = true;
+ }
+
+ @Override
+ public void notifyContentBlocked(@NonNull TvContentRating rating) {
+ this.blockedContentRating = rating;
+ }
+
+ @Override
+ public void notifyTimeShiftStatusChanged(int status) {
+ this.timeShiftStatus = status;
+ }
+
+ @Override
+ public void setOverlayViewEnabled(boolean enabled) {
+ this.overlayViewEnabled = enabled;
+ }
+ }
+
+ private static final class TestSession extends TifSession {
+
+ private TestSession(TifSessionCallbacks callbacks) {
+ super(callbacks);
+ }
+
+ @Override
+ public boolean onSetSurface(@Nullable Surface surface) {
+ return false;
+ }
+
+ @Override
+ public void onSurfaceChanged(int format, int width, int height) {}
+
+ @Override
+ public void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume) {}
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ return false;
+ }
+
+ @Override
+ public void onSetCaptionEnabled(boolean enabled) {}
+
+ @Override
+ public void onUnblockContent(TvContentRating unblockedRating) {}
+
+ public void helpTestSetOverlayViewEnabled(boolean enabled) {
+ super.setOverlayViewEnabled(enabled);
+ }
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/support/tis/WrappedSessionTest.java b/common/tests/robotests/src/com/android/tv/common/support/tis/WrappedSessionTest.java
new file mode 100644
index 0000000..6d09575
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/support/tis/WrappedSessionTest.java
@@ -0,0 +1,188 @@
+package com.android.tv.common.support.tis;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.PlaybackParams;
+import android.media.tv.TvContentRating;
+import android.net.Uri;
+import android.os.Build;
+import android.view.View;
+import com.android.tv.common.support.tis.TifSession.TifSessionCallbacks;
+import com.android.tv.common.support.tis.TifSession.TifSessionFactory;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link WrappedSession}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(minSdk = Build.VERSION_CODES.M, maxSdk = Build.VERSION_CODES.P)
+public class WrappedSessionTest {
+
+ @Mock TifSession mockDelegate;
+ private TifSessionFactory sessionFactory =
+ new TifSessionFactory() {
+ @Override
+ public TifSession create(TifSessionCallbacks callbacks, String inputId) {
+ return mockDelegate;
+ }
+ };
+ private WrappedSession wrappedSession;
+ private SimpleSessionManager sessionManager = new SimpleSessionManager(1);
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ wrappedSession =
+ new WrappedSession(
+ RuntimeEnvironment.application,
+ sessionManager,
+ sessionFactory,
+ "testInputId");
+ }
+
+ @Test
+ public void onRelease() {
+ sessionManager.addSession(wrappedSession);
+ assertThat(sessionManager.getSessionCount()).isEqualTo(1);
+ wrappedSession.onRelease();
+ assertThat(sessionManager.getSessionCount()).isEqualTo(0);
+ Mockito.verify(mockDelegate).onRelease();
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ public void onSetSurface() {
+ wrappedSession.onSetSurface(null);
+ Mockito.verify(mockDelegate).onSetSurface(null);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ public void onSurfaceChanged() {
+ wrappedSession.onSurfaceChanged(1, 2, 3);
+ Mockito.verify(mockDelegate).onSurfaceChanged(1, 2, 3);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ public void onSetStreamVolume() {
+ wrappedSession.onSetStreamVolume(.8f);
+ Mockito.verify(mockDelegate).onSetStreamVolume(.8f);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ public void onTune() {
+ Uri uri = Uri.EMPTY;
+ wrappedSession.onTune(uri);
+ Mockito.verify(mockDelegate).onTune(uri);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ public void onSetCaptionEnabled() {
+ wrappedSession.onSetCaptionEnabled(true);
+ Mockito.verify(mockDelegate).onSetCaptionEnabled(true);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.M)
+ public void onTimeShiftGetCurrentPosition() {
+ Mockito.when(mockDelegate.onTimeShiftGetCurrentPosition()).thenReturn(7L);
+ assertThat(wrappedSession.onTimeShiftGetCurrentPosition()).isEqualTo(7L);
+ Mockito.verify(mockDelegate).onTimeShiftGetCurrentPosition();
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.M)
+ public void onTimeShiftGetStartPosition() {
+ Mockito.when(mockDelegate.onTimeShiftGetStartPosition()).thenReturn(8L);
+ assertThat(wrappedSession.onTimeShiftGetStartPosition()).isEqualTo(8L);
+ Mockito.verify(mockDelegate).onTimeShiftGetStartPosition();
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.M)
+ public void onTimeShiftPause() {
+ wrappedSession.onTimeShiftPause();
+ Mockito.verify(mockDelegate).onTimeShiftPause();
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.M)
+ public void onTimeShiftResume() {
+ wrappedSession.onTimeShiftResume();
+ Mockito.verify(mockDelegate).onTimeShiftResume();
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.M)
+ public void onTimeShiftSeekTo() {
+ wrappedSession.onTimeShiftSeekTo(9L);
+ Mockito.verify(mockDelegate).onTimeShiftSeekTo(9L);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.M)
+ public void onTimeShiftSetPlaybackParams() {
+ PlaybackParams paras = new PlaybackParams();
+ wrappedSession.onTimeShiftSetPlaybackParams(paras);
+ Mockito.verify(mockDelegate).onTimeShiftSetPlaybackParams(paras);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.M)
+ public void onUnblockContent() {
+ TvContentRating rating =
+ TvContentRating.createRating(
+ "domain", "rating_system", "rating", "subrating1", "subrating2");
+ wrappedSession.onUnblockContent(rating);
+ Mockito.verify(mockDelegate).onUnblockContent(rating);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ public void onParentalControlsChanged() {
+ wrappedSession.onParentalControlsChanged();
+ Mockito.verify(mockDelegate).onParentalControlsChanged();
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ public void testSetOverlayViewEnabled() {
+ wrappedSession.setOverlayViewEnabled(true);
+ // Just verifying that the call completes.
+ }
+
+ @Test
+ public void testOnCreateOverlayView() {
+ View v = new View(RuntimeEnvironment.application);
+ Mockito.when(mockDelegate.onCreateOverlayView()).thenReturn(v);
+
+ View actualView = wrappedSession.onCreateOverlayView();
+
+ assertThat(actualView).isEqualTo(v);
+ Mockito.verify(mockDelegate).onCreateOverlayView();
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+
+ @Test
+ public void testOnOverlayViewSizeChanged() {
+ wrappedSession.onOverlayViewSizeChanged(5 /* width */, 13 /* height */);
+ Mockito.verify(mockDelegate).onOverlayViewSizeChanged(5, 13);
+ Mockito.verifyNoMoreInteractions(mockDelegate);
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/support/tvprovider/TvContractCompatXTest.java b/common/tests/robotests/src/com/android/tv/common/support/tvprovider/TvContractCompatXTest.java
new file mode 100644
index 0000000..db32be6
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/support/tvprovider/TvContractCompatXTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 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.common.support.tvprovider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import com.android.tv.testing.constants.ConfigConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link TvContractCompatX}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class TvContractCompatXTest {
+
+ @Test
+ public void buildChannelUri() {
+ assertThat(TvContractCompatX.buildChannelUri("com.example", "foo"))
+ .isEqualTo(
+ Uri.parse(
+ "content://android.media.tv/channel?"
+ + "package=com.example&internal_provider_id=foo"));
+ }
+
+ @Test
+ public void buildProgramsUriForChannel() {
+ assertThat(TvContractCompatX.buildProgramsUriForChannel("com.example", "foo"))
+ .isEqualTo(
+ Uri.parse(
+ "content://android.media.tv/program?"
+ + "package=com.example&internal_provider_id=foo"));
+ }
+
+ @Test
+ public void buildProgramsUriForChannel_period() {
+ assertThat(TvContractCompatX.buildProgramsUriForChannel("com.example", "foo", 1234L, 5467L))
+ .isEqualTo(
+ Uri.parse(
+ "content://android.media.tv/program?"
+ + "package=com.example&internal_provider_id=foo"
+ + "&start_time=1234&end_time=5467"));
+ }
+
+ @Test
+ public void buildProgramsUri_period() {
+ assertThat(TvContractCompatX.buildProgramsUri(1234L, 5467L))
+ .isEqualTo(
+ Uri.parse(
+ "content://android.media.tv/program?"
+ + "start_time=1234&end_time=5467"));
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/util/CommonUtilsTest.java b/common/tests/robotests/src/com/android/tv/common/util/CommonUtilsTest.java
new file mode 100644
index 0000000..0b793db
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/util/CommonUtilsTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 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.common.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link CommonUtils}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class CommonUtilsTest {
+
+ @Test
+ public void deleteDirOrFile_file() throws IOException {
+ File file = new File(RuntimeEnvironment.application.getExternalFilesDir(null), "file");
+ assertThat(file.createNewFile()).isTrue();
+ assertThat(CommonUtils.deleteDirOrFile(file)).isTrue();
+ assertThat(file.exists()).isFalse();
+ }
+
+ @Test
+ public void deleteDirOrFile_Dir() throws IOException {
+ File dir = new File(RuntimeEnvironment.application.getExternalFilesDir(null), "dir");
+ assertThat(dir.mkdirs()).isTrue();
+ assertThat(new File(dir, "file").createNewFile()).isTrue();
+ assertThat(CommonUtils.deleteDirOrFile(dir)).isTrue();
+ assertThat(dir.exists()).isFalse();
+ }
+
+ @Test
+ public void deleteDirOrFile_unreadableDir() throws IOException {
+ File dir = new File(RuntimeEnvironment.application.getExternalFilesDir(null), "dir");
+ assertThat(dir.mkdirs()).isTrue();
+ assertThat(new File(dir, "file").createNewFile()).isTrue();
+ dir.setReadable(false);
+ // Since dir is unreadable dir.listFiles() returns null and file is not deleted thus
+ // dir is not actually deleted.
+ assertThat(CommonUtils.deleteDirOrFile(dir)).isFalse();
+ assertThat(dir.exists()).isTrue();
+ }
+
+ @Test
+ public void deleteDirOrFile_unreadableSubDir() throws IOException {
+ File dir = new File(RuntimeEnvironment.application.getExternalFilesDir(null), "dir");
+ File subDir = new File(dir, "sub");
+ assertThat(subDir.mkdirs()).isTrue();
+ File file = new File(subDir, "file");
+ assertThat(file.createNewFile()).isTrue();
+ subDir.setReadable(false);
+ // Since subDir is unreadable subDir.listFiles() returns null and file is not deleted thus
+ // dir is not actually deleted.
+ assertThat(CommonUtils.deleteDirOrFile(dir)).isFalse();
+ assertThat(dir.exists()).isTrue();
+ }
+}
diff --git a/common/tests/robotests/src/com/android/tv/common/util/ContentUriUtilsTest.java b/common/tests/robotests/src/com/android/tv/common/util/ContentUriUtilsTest.java
new file mode 100644
index 0000000..be705a9
--- /dev/null
+++ b/common/tests/robotests/src/com/android/tv/common/util/ContentUriUtilsTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.common.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link ContentUriUtils}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class ContentUriUtilsTest {
+
+ @Test
+ public void safeParseId_empty() {
+ assertThat(ContentUriUtils.safeParseId(Uri.EMPTY)).isEqualTo(-1);
+ }
+
+ @Test
+ public void safeParseId_bad() {
+ assertThat(ContentUriUtils.safeParseId(Uri.parse("foo/bad"))).isEqualTo(-1);
+ }
+
+ @Test
+ public void safeParseId_123() {
+ assertThat(ContentUriUtils.safeParseId(Uri.parse("foo/123"))).isEqualTo(123);
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index 6208234..9d027fe 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,4 +21,5 @@
org.gradle.daemon=true
org.gradle.parallel=true
-org.gradle.configureondemand=true
\ No newline at end of file
+android.useAndroidX=true
+android.enableJetifier=true
\ No newline at end of file
diff --git a/jni/DvbManager.cpp b/jni/DvbManager.cpp
index f9dff59..3320959 100644
--- a/jni/DvbManager.cpp
+++ b/jni/DvbManager.cpp
@@ -42,7 +42,6 @@
mDvrFd(-1),
mPatFilterFd(-1),
mDvbApiVersion(DVB_API_VERSION_UNDEFINED),
- mDeliverySystemType(-1),
mFeHasLock(false),
mHasPendingTune(false) {
jclass clazz = env->FindClass("com/android/tv/tuner/TunerHal");
@@ -50,6 +49,8 @@
env->GetMethodID(clazz, "openDvbFrontEndFd", "()I");
mOpenDvbDemuxMethodID = env->GetMethodID(clazz, "openDvbDemuxFd", "()I");
mOpenDvbDvrMethodID = env->GetMethodID(clazz, "openDvbDvrFd", "()I");
+ memset(&mDeliverySystemTypes, DELIVERY_SYSTEM_UNDEFINED,
+ sizeof(mDeliverySystemTypes));
}
DvbManager::~DvbManager() {
@@ -115,6 +116,20 @@
int DvbManager::tune(JNIEnv *env, jobject thiz,
const int frequency, const char *modulationStr, int timeout_ms) {
+ return tuneInternal(env, thiz, DELIVERY_SYSTEM_UNDEFINED, frequency,
+ modulationStr, timeout_ms);
+}
+
+int DvbManager::tune(JNIEnv *env, jobject thiz,
+ const int deliverySystemType, const int frequency,
+ const char *modulationStr, int timeout_ms) {
+ return tuneInternal(env, thiz, deliverySystemType, frequency,
+ modulationStr, timeout_ms);
+}
+
+int DvbManager::tuneInternal(JNIEnv *env, jobject thiz,
+ const int deliverySystemType, const int frequency,
+ const char *modulationStr, int timeout_ms) {
resetExceptFe();
if (openDvbFe(env, thiz) != 0) {
@@ -146,10 +161,36 @@
struct dtv_property deliverySystemProperty = {
.cmd = DTV_DELIVERY_SYSTEM
};
- deliverySystemProperty.u.data = SYS_ATSC;
+ switch (deliverySystemType) {
+ case DELIVERY_SYSTEM_DVBT:
+ deliverySystemProperty.u.data = SYS_DVBT;
+ break;
+ case DELIVERY_SYSTEM_DVBT2:
+ deliverySystemProperty.u.data = SYS_DVBT2;
+ break;
+ case DELIVERY_SYSTEM_DVBS:
+ deliverySystemProperty.u.data = SYS_DVBS;
+ break;
+ case DELIVERY_SYSTEM_DVBS2:
+ deliverySystemProperty.u.data = SYS_DVBS2;
+ break;
+ case DELIVERY_SYSTEM_DVBC:
+ deliverySystemProperty.u.data = SYS_DVBC_ANNEX_A;
+ break;
+ case DELIVERY_SYSTEM_ATSC:
+ case DELIVERY_SYSTEM_UNDEFINED:
+ deliverySystemProperty.u.data = SYS_ATSC;
+ break;
+ default:
+ ALOGE("Unrecognized delivery system type");
+ return -1;
+ }
struct dtv_property frequencyProperty = {
.cmd = DTV_FREQUENCY
};
+ struct dtv_property bandwidthProperty = {
+ .cmd = DTV_BANDWIDTH_HZ, .u.data = 8000000
+ };
frequencyProperty.u.data = static_cast<__u32>(frequency);
struct dtv_property modulationProperty = { .cmd = DTV_MODULATION };
if (strncmp(modulationStr, "QAM", 3) == 0) {
@@ -163,10 +204,11 @@
struct dtv_property tuneProperty = { .cmd = DTV_TUNE };
struct dtv_property props[] = {
- deliverySystemProperty, frequencyProperty, modulationProperty, tuneProperty
+ deliverySystemProperty, frequencyProperty, modulationProperty,
+ bandwidthProperty, tuneProperty
};
struct dtv_properties dtvProperty = {
- .num = 4, .props = props
+ .num = sizeof(props)/sizeof(dtv_property), .props = props
};
if (mHasPendingTune) {
@@ -215,6 +257,9 @@
ALOGE("Unrecognized modulation mode : %s", modulationStr);
return -1;
}
+ feParams.u.ofdm.code_rate_HP = FEC_AUTO;
+ feParams.u.ofdm.code_rate_LP = FEC_AUTO;
+ feParams.u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO;
break;
default:
ALOGE("Unsupported delivery system.");
@@ -462,22 +507,27 @@
}
int DvbManager::getDeliverySystemType(JNIEnv *env, jobject thiz) {
- if (mDeliverySystemType != -1) {
- return mDeliverySystemType;
+ getDeliverySystemTypes(env, thiz);
+ return mDeliverySystemTypes[0];
+}
+
+int* DvbManager::getDeliverySystemTypes(JNIEnv *env, jobject thiz) {
+ ALOGE("getDeliverySystemTypes");
+ if (mDeliverySystemTypes[0] != DELIVERY_SYSTEM_UNDEFINED) {
+ return mDeliverySystemTypes;
}
if (mFeFd == -1) {
if ((mFeFd = openDvbFeFromSystemApi(env, thiz)) < 0) {
ALOGD("Can't open FE file : %s", strerror(errno));
- return DELIVERY_SYSTEM_UNDEFINED;
+ return mDeliverySystemTypes;
}
}
struct dtv_property testProps[1] = {
- { .cmd = DTV_DELIVERY_SYSTEM }
+ { .cmd = DTV_ENUM_DELSYS }
};
struct dtv_properties feProp = {
.num = 1, .props = testProps
};
- mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED;
if (ioctl(mFeFd, FE_GET_PROPERTY, &feProp) == -1) {
mDvbApiVersion = DVB_API_VERSION3;
if (openDvbFe(env, thiz) == 0) {
@@ -485,50 +535,52 @@
if (ioctl(mFeFd, FE_GET_INFO, &info) == 0) {
switch (info.type) {
case FE_QPSK:
- mDeliverySystemType = DELIVERY_SYSTEM_DVBS;
+ mDeliverySystemTypes[0] = DELIVERY_SYSTEM_DVBS;
break;
case FE_QAM:
- mDeliverySystemType = DELIVERY_SYSTEM_DVBC;
+ mDeliverySystemTypes[0] = DELIVERY_SYSTEM_DVBC;
break;
case FE_OFDM:
- mDeliverySystemType = DELIVERY_SYSTEM_DVBT;
+ mDeliverySystemTypes[0] = DELIVERY_SYSTEM_DVBT;
break;
case FE_ATSC:
- mDeliverySystemType = DELIVERY_SYSTEM_ATSC;
+ mDeliverySystemTypes[0] = DELIVERY_SYSTEM_ATSC;
break;
default:
- mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED;
+ mDeliverySystemTypes[0] = DELIVERY_SYSTEM_UNDEFINED;
break;
}
}
}
} else {
mDvbApiVersion = DVB_API_VERSION5;
- switch (feProp.props[0].u.data) {
- case SYS_DVBT:
- mDeliverySystemType = DELIVERY_SYSTEM_DVBT;
- break;
- case SYS_DVBT2:
- mDeliverySystemType = DELIVERY_SYSTEM_DVBT2;
- break;
- case SYS_DVBS:
- mDeliverySystemType = DELIVERY_SYSTEM_DVBS;
- break;
- case SYS_DVBS2:
- mDeliverySystemType = DELIVERY_SYSTEM_DVBS2;
- break;
- case SYS_DVBC_ANNEX_A:
- case SYS_DVBC_ANNEX_B:
- case SYS_DVBC_ANNEX_C:
- mDeliverySystemType = DELIVERY_SYSTEM_DVBC;
- break;
- case SYS_ATSC:
- mDeliverySystemType = DELIVERY_SYSTEM_ATSC;
- break;
- default:
- mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED;
- break;
+ for (unsigned int i = 0; i < feProp.props[0].u.buffer.len; i++) {
+ switch (feProp.props[0].u.buffer.data[i]) {
+ case SYS_DVBT:
+ mDeliverySystemTypes[i] = DELIVERY_SYSTEM_DVBT;
+ break;
+ case SYS_DVBT2:
+ mDeliverySystemTypes[i] = DELIVERY_SYSTEM_DVBT2;
+ break;
+ case SYS_DVBS:
+ mDeliverySystemTypes[i] = DELIVERY_SYSTEM_DVBS;
+ break;
+ case SYS_DVBS2:
+ mDeliverySystemTypes[i] = DELIVERY_SYSTEM_DVBS2;
+ break;
+ case SYS_DVBC_ANNEX_A:
+ case SYS_DVBC_ANNEX_B:
+ case SYS_DVBC_ANNEX_C:
+ mDeliverySystemTypes[i] = DELIVERY_SYSTEM_DVBC;
+ break;
+ case SYS_ATSC:
+ mDeliverySystemTypes[i] = DELIVERY_SYSTEM_ATSC;
+ break;
+ default:
+ mDeliverySystemTypes[i] = DELIVERY_SYSTEM_UNDEFINED;
+ break;
+ }
}
}
- return mDeliverySystemType;
-}
\ No newline at end of file
+ return mDeliverySystemTypes;
+}
diff --git a/jni/DvbManager.h b/jni/DvbManager.h
index b01113e..aaa345e 100644
--- a/jni/DvbManager.h
+++ b/jni/DvbManager.h
@@ -63,7 +63,7 @@
int mDvrFd;
int mPatFilterFd;
int mDvbApiVersion;
- int mDeliverySystemType;
+ int mDeliverySystemTypes[8];
bool mFeHasLock;
// Flag for pending tune request. Used for canceling the current tune operation.
bool volatile mHasPendingTune;
@@ -78,6 +78,9 @@
~DvbManager();
int tune(JNIEnv *env, jobject thiz,
const int frequency, const char *modulationStr, int timeout_ms);
+ int tune(JNIEnv *env, jobject thiz,
+ const int deliverySystemType, const int frequency,
+ const char *modulationStr, int timeout_ms);
int stopTune();
int readTsStream(JNIEnv *env, jobject thiz,
uint8_t *tsBuffer, int tsBufferSize, int timeout_ms);
@@ -85,9 +88,13 @@
void closeAllDvbPidFilter();
void setHasPendingTune(bool hasPendingTune);
int getDeliverySystemType(JNIEnv *env, jobject thiz);
+ int *getDeliverySystemTypes(JNIEnv *env, jobject thiz);
int getSignalStrength();
private:
+ int tuneInternal(JNIEnv *env, jobject thiz,
+ const int deliverySystemType, const int frequency,
+ const char *modulationStr, int timeout_ms);
int openDvbFe(JNIEnv *env, jobject thiz);
int openDvbDvr(JNIEnv *env, jobject thiz);
void closePatFilter();
diff --git a/jni/tunertvinput_jni.cpp b/jni/tunertvinput_jni.cpp
index 030f961..579b92e 100644
--- a/jni/tunertvinput_jni.cpp
+++ b/jni/tunertvinput_jni.cpp
@@ -49,11 +49,12 @@
/*
* Class: com_android_tv_tuner_TunerHal
* Method: nativeTune
- * Signature: (JILjava/lang/String;)Z
+ * Signature: (JILjava/lang/String;I)Z
*/
-JNIEXPORT jboolean JNICALL Java_com_android_tv_tuner_TunerHal_nativeTune(
- JNIEnv *env, jobject thiz, jlong deviceId, jint frequency,
- jstring modulation, jint timeout_ms) {
+JNIEXPORT jboolean JNICALL
+ Java_com_android_tv_tuner_TunerHal_nativeTune__JILjava_lang_String_2I(
+ JNIEnv *env, jobject thiz, jlong deviceId, jint frequency,
+ jstring modulation, jint timeout_ms) {
std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
DvbManager *dvbManager;
if (it == sDvbManagers.end()) {
@@ -69,6 +70,29 @@
/*
* Class: com_android_tv_tuner_TunerHal
+ * Method: nativeTune
+ * Signature: (JIILjava/lang/String;I)Z
+ */
+JNIEXPORT jboolean JNICALL
+ Java_com_android_tv_tuner_TunerHal_nativeTune__JIILjava_lang_String_2I(
+ JNIEnv *env, jobject thiz, jlong deviceId, jint deliverySystemType,
+ jint frequency, jstring modulation, jint timeout_ms) {
+ std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+ DvbManager *dvbManager;
+ if (it == sDvbManagers.end()) {
+ dvbManager = new DvbManager(env, thiz);
+ sDvbManagers.insert(
+ std::pair<jlong, DvbManager *>(deviceId, dvbManager));
+ } else {
+ dvbManager = it->second;
+ }
+ int res = dvbManager->tune(env, thiz, deliverySystemType, frequency,
+ env->GetStringUTFChars(modulation, 0), timeout_ms);
+ return (res == 0);
+}
+
+/*
+ * Class: com_android_tv_tuner_TunerHal
* Method: nativeCloseAllPidFilters
* Signature: (J)V
*/
@@ -190,4 +214,32 @@
sDvbManagers.insert(std::pair<jlong, DvbManager *>(deviceId, dvbManager));
return dvbManager->getDeliverySystemType(env, thiz);
}
-}
\ No newline at end of file
+}
+
+/*
+ * Class: com_android_tv_tuner_TunerHal
+ * Method: nativeGetDeliverySystemTypes
+ * Signature: (J)I
+ */
+JNIEXPORT jintArray JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemTypes(JNIEnv *env,
+ jobject thiz,
+ jlong deviceId) {
+ jintArray deliverySystemTypes = env->NewIntArray(8);
+ if (deliverySystemTypes == NULL) {
+ ALOGE("Out of memory!");
+ return NULL;
+ }
+ std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+ if (it != sDvbManagers.end()) {
+ env->SetIntArrayRegion(deliverySystemTypes, 0, 8,
+ it->second->getDeliverySystemTypes(env, thiz));
+ } else {
+ DvbManager *dvbManager = new DvbManager(env, thiz);
+ sDvbManagers.insert(
+ std::pair<jlong, DvbManager *>(deviceId, dvbManager));
+ env->SetIntArrayRegion(deliverySystemTypes, 0, 8,
+ dvbManager->getDeliverySystemTypes(env, thiz));
+ }
+ return deliverySystemTypes;
+}
diff --git a/jni/tunertvinput_jni.h b/jni/tunertvinput_jni.h
index 36e631f..02c242b 100755
--- a/jni/tunertvinput_jni.h
+++ b/jni/tunertvinput_jni.h
@@ -66,8 +66,18 @@
* Method: nativeTune
* Signature: (JILjava/lang/String;I)Z
*/
-JNIEXPORT jboolean JNICALL Java_com_android_tv_tuner_TunerHal_nativeTune
- (JNIEnv *, jobject, jlong, jint, jstring, jint);
+JNIEXPORT jboolean JNICALL
+ Java_com_android_tv_tuner_TunerHal_nativeTune__JILjava_lang_String_2I
+ (JNIEnv *, jobject, jlong, jint, jstring, jint);
+
+/*
+ * Class: com_android_tv_tuner_TunerHal
+ * Method: nativeTune
+ * Signature: Signature: (JIILjava/lang/String;I)Z
+ */
+JNIEXPORT jboolean JNICALL
+ Java_com_android_tv_tuner_TunerHal_nativeTune__JIILjava_lang_String_2I
+ (JNIEnv *, jobject, jlong, jint, jint, jstring, jint);
/*
* Class: com_android_tv_tuner_TunerHal
@@ -106,6 +116,15 @@
/*
* Class: com_android_tv_tuner_TunerHal
+ * Method: nativeGetDeliverySystemTypes
+ * Signature: (J)I
+ */
+JNIEXPORT jintArray JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemTypes
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_android_tv_tuner_TunerHal
* Method: nativeGetSignalStrength
* Signature: (J)I
*/
diff --git a/libs/Android.bp b/libs/Android.bp
index fea9487..6e6c0f4 100644
--- a/libs/Android.bp
+++ b/libs/Android.bp
@@ -13,8 +13,15 @@
// limitations under the License.
java_import {
+ name: "tv-auto-common-jar",
+ jars: ["m2/auto-common-0.10.jar"],
+ host_supported: true,
+ sdk_version: "current",
+}
+
+java_import {
name: "tv-auto-factory-jar",
- jars: ["auto-factory-1.0-beta2.jar"],
+ jars: ["m2/auto-factory-1.0-beta6.jar"],
host_supported: true,
sdk_version: "current",
}
@@ -22,20 +29,22 @@
java_plugin {
name: "tv-auto-factory",
static_libs: [
- "jsr330",
+ "jsr330",
+ "tv-auto-common-jar",
"tv-auto-factory-jar",
+ "tv-auto-value-jar",
+ "tv-google-java-format-jar",
"tv-guava-jre-jar",
- "tv-javawriter-jar",
- "tv-javax-annotations-jar",
+ "tv-javapoet-jar",
+ "tv-javax-annotations-jar",
],
processor_class: "com.google.auto.factory.processor.AutoFactoryProcessor",
generates_api: true,
}
-
java_import {
name: "tv-auto-value-jar",
- jars: ["auto-value-1.5.2.jar"],
+ jars: ["m2/auto-value-1.5.3.jar"],
host_supported: true,
sdk_version: "current",
}
@@ -51,26 +60,33 @@
java_import {
name: "tv-error-prone-annotations-jar",
- jars: ["error_prone_annotations-2.3.1.jar"],
+ jars: ["m2/error_prone_annotations-2.3.2.jar"],
sdk_version: "current",
}
java_import {
- name: "tv-guava-jre-jar",
- jars: ["guava-23.3-jre.jar"],
+ name: "tv-google-java-format-jar",
+ jars: ["google-java-format-1.7-all-deps.jar"],
host_supported: true,
sdk_version: "current",
}
java_import {
name: "tv-guava-android-jar",
- jars: ["guava-23.6-android.jar"],
+ jars: ["m2/guava-28.0-android.jar"],
sdk_version: "current",
}
-java_import_host{
- name: "tv-javawriter-jar",
- jars: ["javawriter-2.5.1.jar"],
+java_import {
+ name: "tv-guava-jre-jar",
+ jars: ["m2/guava-28.0-jre.jar"],
+ host_supported: true,
+ sdk_version: "current",
+}
+
+java_import_host {
+ name: "tv-javapoet-jar",
+ jars: ["m2/javapoet-1.11.1.jar"],
}
java_import {
@@ -80,7 +96,6 @@
sdk_version: "current",
}
-
android_library_import {
name: "tv-lib-exoplayer",
aars: ["exoplayer-r1.5.16.aar"],
@@ -89,31 +104,22 @@
android_library_import {
name: "tv-lib-exoplayer-v2-core",
- aars: ["exoplayer-core-2.9.0.aar"],
+ aars: ["exoplayer-core-2.10.1.aar"],
sdk_version: "current",
}
java_import_host {
- name: "tv-lib-dagger-compiler-deps",
- jars: [
- "google-java-format-1.4-all-deps.jar",
- "guava-23.3-jre.jar",
- "javapoet-1.8.0.jar",
- ],
-}
-
-java_import_host {
name: "tv-lib-dagger-compiler-import",
jars: [
- "dagger-compiler-2.15.jar",
- "dagger-producers-2.15.jar",
- "dagger-spi-2.15.jar",
+ "m2/dagger-compiler-2.23.jar",
+ "m2/dagger-producers-2.23.jar",
+ "m2/dagger-spi-2.23.jar",
],
}
java_import {
name: "tv-lib-dagger",
- jars: ["dagger-2.15.jar"],
+ jars: ["m2/dagger-2.23.jar"],
host_supported: true,
sdk_version: "current",
}
@@ -122,26 +128,31 @@
name: "tv-lib-dagger-compiler",
static_libs: [
"tv-lib-dagger-compiler-import",
- "tv-lib-dagger-compiler-deps",
+ "tv-guava-jre-jar",
+ "tv-javapoet-jar",
+ "tv-google-java-format-jar",
"jsr330",
"tv-lib-dagger",
],
processor_class: "dagger.internal.codegen.ComponentProcessor",
generates_api: true,
+ // shade guava to avoid conflicts with guava embedded in Error Prone.
+ jarjar_rules: "m2/dagger-jarjar-rules.txt",
}
android_library_import {
name: "tv-lib-dagger-android",
- aars: ["dagger-android-2.15.aar"],
+ aars: ["m2/dagger-android-2.23.aar"],
sdk_version: "current",
}
java_import_host {
name: "tv-lib-dagger-android-processor-import",
jars: [
- "dagger-android-jarimpl-2.15.jar",
- "dagger-android-processor-2.15.jar",
- "dagger-android-support-jarimpl-2.15.jar",
+ "m2/dagger-android-jarimpl-2.23.jar",
+ "m2/dagger-android-processor-2.23.jar",
+ "m2/dagger-spi-2.23.jar",
+ "m2/protobuf-java-3.7.0.jar",
],
}
@@ -149,17 +160,21 @@
name: "tv-lib-dagger-android-processor",
static_libs: [
"tv-lib-dagger-android-processor-import",
- "tv-lib-dagger-compiler-deps",
+ "tv-guava-jre-jar",
+ "tv-javapoet-jar",
+ "tv-google-java-format-jar",
"jsr330",
"tv-lib-dagger",
],
processor_class: "dagger.android.processor.AndroidProcessor",
generates_api: true,
+ // shade guava to avoid conflicts with guava embedded in Error Prone.
+ jarjar_rules: "m2/dagger-jarjar-rules.txt",
}
java_import {
name: "tv-lib-truth",
- jars: ["truth-0.36.jar"],
+ jars: ["truth-0.45.jar"],
host_supported: true,
sdk_version: "current",
}
diff --git a/libs/auto-factory-1.0-beta2.jar b/libs/auto-factory-1.0-beta2.jar
deleted file mode 100644
index ceaddac..0000000
--- a/libs/auto-factory-1.0-beta2.jar
+++ /dev/null
Binary files differ
diff --git a/libs/auto-value-1.5.2.jar b/libs/auto-value-1.5.2.jar
deleted file mode 100644
index 8ac0567..0000000
--- a/libs/auto-value-1.5.2.jar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-2.15.jar b/libs/dagger-2.15.jar
deleted file mode 100644
index 6d76688..0000000
--- a/libs/dagger-2.15.jar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-android-2.15.aar b/libs/dagger-android-2.15.aar
deleted file mode 100644
index 430294a..0000000
--- a/libs/dagger-android-2.15.aar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-android-jarimpl-2.15.jar b/libs/dagger-android-jarimpl-2.15.jar
deleted file mode 100644
index 7f7cd45..0000000
--- a/libs/dagger-android-jarimpl-2.15.jar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-android-processor-2.15.jar b/libs/dagger-android-processor-2.15.jar
deleted file mode 100644
index 3c7ac05..0000000
--- a/libs/dagger-android-processor-2.15.jar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-android-support-2.15.aar b/libs/dagger-android-support-2.15.aar
deleted file mode 100644
index 89a71a9..0000000
--- a/libs/dagger-android-support-2.15.aar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-android-support-jarimpl-2.15.jar b/libs/dagger-android-support-jarimpl-2.15.jar
deleted file mode 100644
index d0ea01a..0000000
--- a/libs/dagger-android-support-jarimpl-2.15.jar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-compiler-2.15.jar b/libs/dagger-compiler-2.15.jar
deleted file mode 100644
index e73110f..0000000
--- a/libs/dagger-compiler-2.15.jar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-producers-2.15.jar b/libs/dagger-producers-2.15.jar
deleted file mode 100644
index f1dbb07..0000000
--- a/libs/dagger-producers-2.15.jar
+++ /dev/null
Binary files differ
diff --git a/libs/dagger-spi-2.15.jar b/libs/dagger-spi-2.15.jar
deleted file mode 100644
index 6e3156a..0000000
--- a/libs/dagger-spi-2.15.jar
+++ /dev/null
Binary files differ
diff --git a/libs/error_prone_annotations-2.3.1.jar b/libs/error_prone_annotations-2.3.1.jar
deleted file mode 100644
index 8a0efa3..0000000
--- a/libs/error_prone_annotations-2.3.1.jar
+++ /dev/null
Binary files differ
diff --git a/libs/exoplayer-core-2.10.1.aar b/libs/exoplayer-core-2.10.1.aar
new file mode 100644
index 0000000..2342fed
--- /dev/null
+++ b/libs/exoplayer-core-2.10.1.aar
Binary files differ
diff --git a/libs/exoplayer-core-2.9.0.aar b/libs/exoplayer-core-2.9.0.aar
deleted file mode 100644
index 64c4f37..0000000
--- a/libs/exoplayer-core-2.9.0.aar
+++ /dev/null
Binary files differ
diff --git a/libs/google-java-format-1.4-all-deps.jar b/libs/google-java-format-1.4-all-deps.jar
deleted file mode 100644
index b10bfbd..0000000
--- a/libs/google-java-format-1.4-all-deps.jar
+++ /dev/null
Binary files differ
diff --git a/libs/google-java-format-1.7-all-deps.jar b/libs/google-java-format-1.7-all-deps.jar
new file mode 100644
index 0000000..e2d40de
--- /dev/null
+++ b/libs/google-java-format-1.7-all-deps.jar
Binary files differ
diff --git a/libs/guava-23.3-jre.jar b/libs/guava-23.3-jre.jar
deleted file mode 100644
index b13e275..0000000
--- a/libs/guava-23.3-jre.jar
+++ /dev/null
Binary files differ
diff --git a/libs/guava-23.5-jre.jar b/libs/guava-23.5-jre.jar
deleted file mode 100644
index 7e5f13a..0000000
--- a/libs/guava-23.5-jre.jar
+++ /dev/null
Binary files differ
diff --git a/libs/guava-23.6-android.jar b/libs/guava-23.6-android.jar
deleted file mode 100644
index 01180d2..0000000
--- a/libs/guava-23.6-android.jar
+++ /dev/null
Binary files differ
diff --git a/libs/javapoet-1.8.0.jar b/libs/javapoet-1.8.0.jar
deleted file mode 100644
index 6758b6d..0000000
--- a/libs/javapoet-1.8.0.jar
+++ /dev/null
Binary files differ
diff --git a/libs/javawriter-2.5.1.jar b/libs/javawriter-2.5.1.jar
deleted file mode 100644
index 4ec579e..0000000
--- a/libs/javawriter-2.5.1.jar
+++ /dev/null
Binary files differ
diff --git a/libs/m2/animal-sniffer-annotations-1.17.jar b/libs/m2/animal-sniffer-annotations-1.17.jar
new file mode 100644
index 0000000..6ec7a60
--- /dev/null
+++ b/libs/m2/animal-sniffer-annotations-1.17.jar
Binary files differ
diff --git a/libs/m2/auto-common-0.10.jar b/libs/m2/auto-common-0.10.jar
new file mode 100644
index 0000000..8cbfa72
--- /dev/null
+++ b/libs/m2/auto-common-0.10.jar
Binary files differ
diff --git a/libs/m2/auto-factory-1.0-beta6.jar b/libs/m2/auto-factory-1.0-beta6.jar
new file mode 100644
index 0000000..e47130f
--- /dev/null
+++ b/libs/m2/auto-factory-1.0-beta6.jar
Binary files differ
diff --git a/libs/m2/auto-value-1.5.3.jar b/libs/m2/auto-value-1.5.3.jar
new file mode 100644
index 0000000..99eeb0b
--- /dev/null
+++ b/libs/m2/auto-value-1.5.3.jar
Binary files differ
diff --git a/libs/m2/checker-qual-2.8.1.jar b/libs/m2/checker-qual-2.8.1.jar
new file mode 100644
index 0000000..09269be
--- /dev/null
+++ b/libs/m2/checker-qual-2.8.1.jar
Binary files differ
diff --git a/libs/m2/dagger-2.23.jar b/libs/m2/dagger-2.23.jar
new file mode 100644
index 0000000..544ee3d
--- /dev/null
+++ b/libs/m2/dagger-2.23.jar
Binary files differ
diff --git a/libs/m2/dagger-android-2.23.aar b/libs/m2/dagger-android-2.23.aar
new file mode 100644
index 0000000..9578dcd
--- /dev/null
+++ b/libs/m2/dagger-android-2.23.aar
Binary files differ
diff --git a/libs/m2/dagger-android-jarimpl-2.23.jar b/libs/m2/dagger-android-jarimpl-2.23.jar
new file mode 100644
index 0000000..94a2bbe
--- /dev/null
+++ b/libs/m2/dagger-android-jarimpl-2.23.jar
Binary files differ
diff --git a/libs/m2/dagger-android-processor-2.23.jar b/libs/m2/dagger-android-processor-2.23.jar
new file mode 100644
index 0000000..500149c
--- /dev/null
+++ b/libs/m2/dagger-android-processor-2.23.jar
Binary files differ
diff --git a/libs/m2/dagger-compiler-2.23.jar b/libs/m2/dagger-compiler-2.23.jar
new file mode 100644
index 0000000..b7cb162
--- /dev/null
+++ b/libs/m2/dagger-compiler-2.23.jar
Binary files differ
diff --git a/libs/m2/dagger-jarjar-rules.txt b/libs/m2/dagger-jarjar-rules.txt
new file mode 100644
index 0000000..618c243
--- /dev/null
+++ b/libs/m2/dagger-jarjar-rules.txt
@@ -0,0 +1,4 @@
+# shade guava to avoid conflicts with guava embedded in Error Prone.
+rule com.google.common.** com.google.dagger.common.@1
+rule com.google.auto.** com.google.dagger.auto.@1
+
diff --git a/libs/m2/dagger-producers-2.23.jar b/libs/m2/dagger-producers-2.23.jar
new file mode 100644
index 0000000..cb1cef9
--- /dev/null
+++ b/libs/m2/dagger-producers-2.23.jar
Binary files differ
diff --git a/libs/m2/dagger-spi-2.23.jar b/libs/m2/dagger-spi-2.23.jar
new file mode 100644
index 0000000..6af1494
--- /dev/null
+++ b/libs/m2/dagger-spi-2.23.jar
Binary files differ
diff --git a/libs/m2/error_prone_annotations-2.3.2.jar b/libs/m2/error_prone_annotations-2.3.2.jar
new file mode 100644
index 0000000..bc2584d
--- /dev/null
+++ b/libs/m2/error_prone_annotations-2.3.2.jar
Binary files differ
diff --git a/libs/m2/failureaccess-1.0.1.jar b/libs/m2/failureaccess-1.0.1.jar
new file mode 100644
index 0000000..9b56dc7
--- /dev/null
+++ b/libs/m2/failureaccess-1.0.1.jar
Binary files differ
diff --git a/libs/m2/guava-28.0-android.jar b/libs/m2/guava-28.0-android.jar
new file mode 100644
index 0000000..516fc5f
--- /dev/null
+++ b/libs/m2/guava-28.0-android.jar
Binary files differ
diff --git a/libs/m2/guava-28.0-jre.jar b/libs/m2/guava-28.0-jre.jar
new file mode 100644
index 0000000..f254aae
--- /dev/null
+++ b/libs/m2/guava-28.0-jre.jar
Binary files differ
diff --git a/libs/m2/j2objc-annotations-1.3.jar b/libs/m2/j2objc-annotations-1.3.jar
new file mode 100644
index 0000000..a429c72
--- /dev/null
+++ b/libs/m2/j2objc-annotations-1.3.jar
Binary files differ
diff --git a/libs/m2/javac-shaded-9-dev-r4023-3.jar b/libs/m2/javac-shaded-9-dev-r4023-3.jar
new file mode 100644
index 0000000..d7b3fd8
--- /dev/null
+++ b/libs/m2/javac-shaded-9-dev-r4023-3.jar
Binary files differ
diff --git a/libs/m2/javapoet-1.11.1.jar b/libs/m2/javapoet-1.11.1.jar
new file mode 100644
index 0000000..27a18e8
--- /dev/null
+++ b/libs/m2/javapoet-1.11.1.jar
Binary files differ
diff --git a/libs/m2/jsr305-3.0.2.jar b/libs/m2/jsr305-3.0.2.jar
new file mode 100644
index 0000000..59222d9
--- /dev/null
+++ b/libs/m2/jsr305-3.0.2.jar
Binary files differ
diff --git a/libs/m2/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar b/libs/m2/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
new file mode 100644
index 0000000..45832c0
--- /dev/null
+++ b/libs/m2/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
Binary files differ
diff --git a/libs/m2/pom-jre.xml b/libs/m2/pom-jre.xml
new file mode 100644
index 0000000..2d834c3
--- /dev/null
+++ b/libs/m2/pom-jre.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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
+ -->
+
+<!-- JRE version of libs, in particular guava -->
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.android.tv</groupId>
+ <artifactId>jre-libs</artifactId>
+ <version>1</version>
+
+ <repositories>
+ <repository>
+ <id>google</id>
+ <name>Google Maven Repository</name>
+ <url>https://maven.google.com</url>
+ <layout>default</layout>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>28.0-jre</version>
+ </dependency>
+
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/libs/m2/pom.xml b/libs/m2/pom.xml
new file mode 100644
index 0000000..5a232d5
--- /dev/null
+++ b/libs/m2/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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
+ -->
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.android.tv</groupId>
+ <artifactId>libs</artifactId>
+ <version>1</version>
+
+ <repositories>
+ <repository>
+ <id>google</id>
+ <name>Google Maven Repository</name>
+ <url>https://maven.google.com</url>
+ <layout>default</layout>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.auto</groupId>
+ <artifactId>auto-common</artifactId>
+ <version>0.10</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.auto.factory</groupId>
+ <artifactId>auto-factory</artifactId>
+ <version>1.0-beta6</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.dagger</groupId>
+ <artifactId>dagger</artifactId>
+ <version>2.23</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.dagger</groupId>
+ <artifactId>dagger-android</artifactId>
+ <type>aar</type>
+ <version>2.23</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.dagger</groupId>
+ <artifactId>dagger-android-jarimpl</artifactId>
+ <version>2.23</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.dagger</groupId>
+ <artifactId>dagger-android-processor</artifactId>
+ <version>2.23</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.dagger</groupId>
+ <artifactId>dagger-compiler</artifactId>
+ <version>2.23</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.dagger</groupId>
+ <artifactId>dagger-producers</artifactId>
+ <version>2.23</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.dagger</groupId>
+ <artifactId>dagger-spi</artifactId>
+ <version>2.23</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>28.0-android</version>
+ </dependency>
+
+ <!-- The host version of guava is listed in pom-jre.xml -->
+
+ <dependency>
+ <groupId>com.squareup</groupId>
+ <artifactId>javapoet</artifactId>
+ <version>1.11.1</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/libs/m2/protobuf-java-3.7.0.jar b/libs/m2/protobuf-java-3.7.0.jar
new file mode 100644
index 0000000..eebaefe
--- /dev/null
+++ b/libs/m2/protobuf-java-3.7.0.jar
Binary files differ
diff --git a/libs/m2/update.sh b/libs/m2/update.sh
new file mode 100755
index 0000000..ee455c7
--- /dev/null
+++ b/libs/m2/update.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+
+git rm *.jar *.aar
+
+EXCLUDES=google-java-format,javax.inject,support-annotations,jsr250-api,checker-compat-qual
+
+mvn \
+ -DoutputDirectory=${pwd} \
+ dependency:copy-dependencies \
+ -DincludeScope=runtime \
+ -DexcludeArtifactIds=${EXCLUDES}
+
+mvn \
+ -DoutputDirectory=${pwd} \
+ -f pom-jre.xml \
+ dependency:copy-dependencies \
+ -DincludeScope=runtime \
+ -DexcludeArtifactIds=${EXCLUDES}
+
+git add *.jar *.aar
+
+
diff --git a/libs/truth-0.36.jar b/libs/truth-0.36.jar
deleted file mode 100644
index 8174e4a..0000000
--- a/libs/truth-0.36.jar
+++ /dev/null
Binary files differ
diff --git a/libs/truth-0.45.jar b/libs/truth-0.45.jar
new file mode 100644
index 0000000..76e4da8
--- /dev/null
+++ b/libs/truth-0.45.jar
Binary files differ
diff --git a/partner_support/AndroidManifest.xml b/partner_support/AndroidManifest.xml
index 45a693f..8f80708 100644
--- a/partner_support/AndroidManifest.xml
+++ b/partner_support/AndroidManifest.xml
@@ -17,6 +17,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.tv.partner.support"
android:versionCode="1">
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application />
</manifest>
diff --git a/partner_support/g3doc/CloudEpgForPartners.md b/partner_support/g3doc/CloudEpgForPartners.md
deleted file mode 100644
index bec6b50..0000000
--- a/partner_support/g3doc/CloudEpgForPartners.md
+++ /dev/null
@@ -1,112 +0,0 @@
-# 3rd party instructions for using Cloud EPG feature of Live Channels
-
-Partners can ask Live Channels to retrieve EPG data for their TV Input Service
-using live channels
-
-## Prerequisites
-
-* Updated agreement with Google
-* Oreo or patched Nougat
-
-## Nougat
-
-To use cloud epg with Nougat you will need the following changes.
-
-### Patch TVProvider
-
-To run in Nougat you must cherry pick [change
-455319](https://android-review.googlesource.com/c/platform/packages/providers/TvProvider/+/455319)
-to TV Provider.
-
-### Customisation
-
-Indicate TvProvider is patched by including the following in their TV
-customization resource
-
-```
-<bool name="tvprovider_allows_system_inserts_to_program_table">true</bool>
-```
-
-See https://source.android.com/devices/tv/customize-tv-app
-
-## **Input Setup**
-
-During the input setup activity, the TIS will query the content provider for
-lineups in a given postal code. The TIS then inserts a row to the inputs table
-with input_id and lineup_id
-
-On completion of the activity the TIS sets the extra data in the result to
-
-* `com.android.tv.extra.USE_CLOUD_EPG = true`
-* `TvInputInfo.EXTRA_INPUT_ID` with their input_id
-
-This is used to tell Live Channels to immediately start the EPG fetch for that
-input.
-
-### Sample Input Setup code.
-
-A complete sample is at
-../third_party/samples/src/com/example/partnersupportsampletvinput
-
-#### query lineup
-
-```java
- private AsyncTask<Void, Void, List<Lineup>> createFetchLineupsTask() {
- return new AsyncTask<Void, Void, List<Lineup>>() {
- @Override
- protected List<Lineup> doInBackground(Void... params) {
- ContentResolver cr = getActivity().getContentResolver();
-
- List<Lineup> results = new ArrayList<>();
- Cursor cursor =
- cr.query(
- Uri.parse(
- "content://com.android.tv.data.epg/lineups/postal_code/"
- + ZIP),
- null,
- null,
- null,
- null);
-
- while (cursor.moveToNext()) {
- String id = cursor.getString(0);
- String name = cursor.getString(1);
- String channels = cursor.getString(2);
- results.add(new Lineup(id, name, channels));
- }
-
- return results;
- }
-
- @Override
- protected void onPostExecute(List<Lineup> lineups) {
- showLineups(lineups);
- }
- };
- }
-```
-
-#### Insert cloud_epg_input
-
-```java
-ContentValues values = new ContentValues();
-values.put(EpgContract.EpgInputs.COLUMN_INPUT_ID, SampleTvInputService.INPUT_ID);
-values.put(EpgContract.EpgInputs.COLUMN_LINEUP_ID, lineup.getId());
-ContentResolver contentResolver = getActivity().getContentResolver();
-EpgInput epgInput = EpgInputs.queryEpgInput(contentResolver, SampleTvInputService.INPUT_ID);
-if (epgInput == null) {
- contentResolver.insert(EpgContract.EpgInputs.CONTENT_URI, values);
-} else {
- values.put(EpgContract.EpgInputs.COLUMN_ID, epgInput.getId());
- EpgInputs.update(contentResolver, EpgInput.createEpgChannel(values));
-}
-```
-
-#### Return use_cloud_epg
-
-```java
-Intent data = new Intent();
-data.putExtra(TvInputInfo.EXTRA_INPUT_ID, inputId);
-data.putExtra(com.android.tv.extra.USE_CLOUD_EPG, true);
-setResult(Activity.RESULT_OK, data);
-```
diff --git a/partner_support/g3doc/SeriesIdColumnForPartners.md b/partner_support/g3doc/SeriesIdColumnForPartners.md
deleted file mode 100644
index cd44db0..0000000
--- a/partner_support/g3doc/SeriesIdColumnForPartners.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# 3rd party instructions for using series recording feature of Live Channels
-
-## Prerequisites
-
-* Updated agreement with Google
-* Oreo or patched Nougat
-
-## Nougat
-
-To enable series recording with Nougat you will need the following changes.
-
-### Patch TVProvider
-
-To run in Nougat you must backport the following changes
-
-* [Filter out non-existing customized columns in
- DB](https://partner-android.googlesource.com/platform/packages/providers/TvProvider/+/142162af889b2c124bb012eea608c6a65eed54bb)
-* [Add TvProvider methods to get and add
- columns](https://partner-android.googlesource.com/platform/packages/providers/TvProvider/+/cda6788ae903513a555fd3e07a5a1c14218c40a2)
-
-### Customisation
-
-Indicate TvProvider is patched by including the following in their TV
-customization resource
-
-```
-<bool name="tvprovider_allows_column_creation">true</bool>
-```
-
-See https://source.android.com/devices/tv/customize-tv-app
diff --git a/partner_support/g3doc/TurnOffEmbeddedTuner.md b/partner_support/g3doc/TurnOffEmbeddedTuner.md
deleted file mode 100644
index 0ba7cff..0000000
--- a/partner_support/g3doc/TurnOffEmbeddedTuner.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# 3rd party instructions turning off the embedded tuner in Live Channels
-
-Partners that have a built in tuner should provide a TV Input like
-SampleDvbTuner. When partners provide their own tuner they MUST turn of the
-embedded tuner in Live Channels.
-
-### Customisation
-
-Indicate Live Channels should not use it's embedded tuner implementation.
-
-```
-<bool name="turn_off_embedded_tuner">true</bool>
-```
-
-See https://source.android.com/devices/tv/customize-tv-app
diff --git a/partner_support/sample_customization/AndroidManifest.xml b/partner_support/sample_customization/AndroidManifest.xml
index 804691a..5e4c2c7 100644
--- a/partner_support/sample_customization/AndroidManifest.xml
+++ b/partner_support/sample_customization/AndroidManifest.xml
@@ -24,7 +24,7 @@
<uses-feature android:name="android.software.leanback" android:required="true" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application android:label="Partner Customization"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
diff --git a/partner_support/samples/Android.bp b/partner_support/samples/Android.bp
new file mode 100644
index 0000000..9c1d2db
--- /dev/null
+++ b/partner_support/samples/Android.bp
@@ -0,0 +1,25 @@
+android_app {
+ name: "PartnerSupportSampleTvInput",
+
+ // Include all java files.
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.leanback_leanback",
+ "androidx.tvprovider_tvprovider",
+ "live-channels-partner-support",
+ ],
+
+ optimize: {
+ enabled: false,
+ },
+ // Overlay view related functionality requires system APIs.
+ sdk_version: "system_current",
+ min_sdk_version: "23", // M
+
+ // Required for com.android.tv.permission.RECEIVE_INPUT_EVENT
+ privileged: true,
+
+ resource_dirs: ["res"],
+
+}
diff --git a/partner_support/samples/Android.mk b/partner_support/samples/Android.mk
deleted file mode 100644
index 2e771a5..0000000
--- a/partner_support/samples/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include all java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := PartnerSupportSampleTvInput
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-annotations \
- live-channels-partner-support
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-compat \
- android-support-core-ui \
- android-support-v7-recyclerview \
- android-support-v17-leanback \
- androidx.tvprovider_tvprovider
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_PROGUARD_ENABLED := disabled
-# Overlay view related functionality requires system APIs.
-LOCAL_SDK_VERSION := system_current
-LOCAL_MIN_SDK_VERSION := 23 # M
-
-# Required for com.android.tv.permission.RECEIVE_INPUT_EVENT
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-include $(BUILD_PACKAGE)
diff --git a/partner_support/samples/AndroidManifest.xml b/partner_support/samples/AndroidManifest.xml
index d91c603..65b2a3b 100644
--- a/partner_support/samples/AndroidManifest.xml
+++ b/partner_support/samples/AndroidManifest.xml
@@ -29,7 +29,7 @@
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<uses-permission android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT" />
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<!--TODO(b/68949299): remove tool hint when we have smaller dependency targets-->
<application android:label="@string/partner_support_sample_tv_input"
tools:replace="android:label,icon,theme,appComponentFactory"
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java
index ec7589c..d449bb5 100644
--- a/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java
@@ -23,10 +23,10 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/LineupSelectionFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/LineupSelectionFragment.java
index 8c3ca77..7486a98 100644
--- a/partner_support/samples/src/com/example/partnersupportsampletvinput/LineupSelectionFragment.java
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/LineupSelectionFragment.java
@@ -24,21 +24,24 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+
import com.google.android.tv.partner.support.EpgContract;
import com.google.android.tv.partner.support.EpgInput;
import com.google.android.tv.partner.support.EpgInputs;
import com.google.android.tv.partner.support.Lineup;
import com.google.android.tv.partner.support.Lineups;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -176,7 +179,7 @@
List<Lineup> lineups, List<String> localChannels) {
List<Pair<Lineup, Integer>> result = new ArrayList<>();
for (Lineup lineup : lineups) {
- result.add(new Pair<>(lineup, getMatchCount(lineup.getChannels(), localChannels)));
+ result.add(Pair.create(lineup, getMatchCount(lineup.getChannels(), localChannels)));
}
// sort in decreasing order
Collections.sort(
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/LocationFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/LocationFragment.java
index 9492e7e..532ff9b 100644
--- a/partner_support/samples/src/com/example/partnersupportsampletvinput/LocationFragment.java
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/LocationFragment.java
@@ -19,10 +19,10 @@
import android.app.FragmentManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import java.util.List;
/** Location Fragment for users to enter postal code */
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/ResultFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/ResultFragment.java
index a1c17ac..0c89318 100644
--- a/partner_support/samples/src/com/example/partnersupportsampletvinput/ResultFragment.java
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/ResultFragment.java
@@ -21,10 +21,10 @@
import android.media.tv.TvInputInfo;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import com.google.android.tv.partner.support.EpgContract;
import java.util.List;
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputSetupActivity.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputSetupActivity.java
index a0a7588..e11ebdf 100644
--- a/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputSetupActivity.java
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputSetupActivity.java
@@ -18,7 +18,7 @@
import android.app.Activity;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
+import androidx.leanback.app.GuidedStepFragment;
/** The setup activity for partner support sample TV input. */
public class SampleTvInputSetupActivity extends Activity {
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/WelcomeFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/WelcomeFragment.java
index 286f34f..96632d3 100644
--- a/partner_support/samples/src/com/example/partnersupportsampletvinput/WelcomeFragment.java
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/WelcomeFragment.java
@@ -19,10 +19,10 @@
import android.app.FragmentManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import java.util.List;
/** Welcome Fragment shows welcome information for users */
diff --git a/partner_support/src/com/google/android/tv/partner/support/EpgInput.java b/partner_support/src/com/google/android/tv/partner/support/EpgInput.java
index 20b3542..c591c9f 100644
--- a/partner_support/src/com/google/android/tv/partner/support/EpgInput.java
+++ b/partner_support/src/com/google/android/tv/partner/support/EpgInput.java
@@ -20,7 +20,7 @@
import com.google.auto.value.AutoValue;
/**
- * Value class representing a TV Input that uses Live TV EPG.
+ * Value class representing a TV Input that uses TV app EPG.
*
* @see {@link EpgContract.EpgInputs}
*/
diff --git a/partner_support/src/com/google/android/tv/partner/support/TunerSetupUtils.java b/partner_support/src/com/google/android/tv/partner/support/TunerSetupUtils.java
index e25d583..d50db7d 100644
--- a/partner_support/src/com/google/android/tv/partner/support/TunerSetupUtils.java
+++ b/partner_support/src/com/google/android/tv/partner/support/TunerSetupUtils.java
@@ -20,6 +20,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -39,7 +40,7 @@
List<List<String>> parsedLocalChannels = parseChannelNumbers(localChannels);
for (Lineup lineup : lineups) {
result.add(
- new Pair<>(lineup, getMatchCount(lineup.getChannels(), parsedLocalChannels)));
+ Pair.create(lineup, getMatchCount(lineup.getChannels(), parsedLocalChannels)));
}
// sort in decreasing order
Collections.sort(
diff --git a/res/layout/details_overview.xml b/res/layout/details_overview.xml
index dbcf205..541f3fb 100644
--- a/res/layout/details_overview.xml
+++ b/res/layout/details_overview.xml
@@ -55,7 +55,7 @@
android:orientation="vertical" >
<!-- layout_marginStart and layout_marginEnd are overridden -->
- <android.support.v17.leanback.widget.NonOverlappingFrameLayout
+ <androidx.leanback.widget.NonOverlappingFrameLayout
android:id="@+id/details_overview_description"
android:layout_width="match_parent"
android:layout_height="0dp"
@@ -69,7 +69,7 @@
<!-- horizontalSpacing is defined as @dimen/lb_details_overview_action_items_spacing
in newer versions of Leanback Library than LC uses. -->
- <android.support.v17.leanback.widget.HorizontalGridView
+ <androidx.leanback.widget.HorizontalGridView
android:id="@+id/details_overview_actions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/res/layout/dvr_details_description.xml b/res/layout/dvr_details_description.xml
index ee74952..e37d8b0 100644
--- a/res/layout/dvr_details_description.xml
+++ b/res/layout/dvr_details_description.xml
@@ -21,7 +21,7 @@
android:layout_height="wrap_content">
<!-- Top margins are set programatically -->
- <android.support.v17.leanback.widget.ResizingTextView
+ <androidx.leanback.widget.ResizingTextView
android:id="@+id/dvr_details_description_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/res/layout/item_list.xml b/res/layout/item_list.xml
index c06b29a..233aa41 100644
--- a/res/layout/item_list.xml
+++ b/res/layout/item_list.xml
@@ -33,7 +33,7 @@
and compensate the same space with padding.
The accurate layout height is set in MenuRowView.onBind(). -->
- <android.support.v17.leanback.widget.HorizontalGridView
+ <androidx.leanback.widget.HorizontalGridView
android:id="@+id/list_view"
style="@style/menu_row_contents_view"
android:clipChildren="false"
diff --git a/res/layout/option_fragment.xml b/res/layout/option_fragment.xml
index 4a4cbbd..50bf991 100644
--- a/res/layout/option_fragment.xml
+++ b/res/layout/option_fragment.xml
@@ -34,7 +34,7 @@
android:textColor="@color/option_item_text_color"
android:singleLine="true" />
- <android.support.v17.leanback.widget.VerticalGridView
+ <androidx.leanback.widget.VerticalGridView
android:id="@+id/side_panel_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/res/layout/pin_dialog.xml b/res/layout/pin_dialog.xml
index d40d70e..84807f2 100644
--- a/res/layout/pin_dialog.xml
+++ b/res/layout/pin_dialog.xml
@@ -55,8 +55,8 @@
android:fontFamily="@string/font"
android:singleLine="false" />
- <com.android.tv.dialog.picker.PinPicker
- android:id="@+id/pin_picker"
+ <com.android.tv.dialog.picker.TvPinPicker
+ android:id="@+id/tv_pin_picker"
android:importantForAccessibility="yes"
android:layout_width="match_parent"
android:layout_height="154dp"
diff --git a/res/layout/priority_settings_action_item.xml b/res/layout/priority_settings_action_item.xml
index 0f51731..fc882d9 100644
--- a/res/layout/priority_settings_action_item.xml
+++ b/res/layout/priority_settings_action_item.xml
@@ -15,13 +15,13 @@
limitations under the License.
-->
-<android.support.v17.leanback.widget.GuidedActionItemContainer
+<androidx.leanback.widget.GuidedActionItemContainer
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/guidedActionItemContainerStyle"
android:foreground="@null"
android:outlineProvider="background">
- <android.support.v17.leanback.widget.CheckableImageView
+ <androidx.leanback.widget.CheckableImageView
android:id="@+id/guidedactions_item_checkmark"
style="?attr/guidedActionItemCheckmarkStyle"
tools:ignore="ContentDescription" />
@@ -30,16 +30,16 @@
style="?attr/guidedActionItemIconStyle"
tools:ignore="ContentDescription" />
- <android.support.v17.leanback.widget.NonOverlappingLinearLayout
+ <androidx.leanback.widget.NonOverlappingLinearLayout
android:id="@+id/guidedactions_item_content"
style="?attr/guidedActionItemContentStyle" >
- <android.support.v17.leanback.widget.GuidedActionEditText
+ <androidx.leanback.widget.GuidedActionEditText
android:id="@+id/guidedactions_item_title"
style="?attr/guidedActionItemTitleStyle" />
- <android.support.v17.leanback.widget.GuidedActionEditText
+ <androidx.leanback.widget.GuidedActionEditText
android:id="@+id/guidedactions_item_description"
style="?attr/guidedActionItemDescriptionStyle" />
- </android.support.v17.leanback.widget.NonOverlappingLinearLayout>
+ </androidx.leanback.widget.NonOverlappingLinearLayout>
<ImageView
android:id="@+id/guidedactions_item_chevron"
@@ -53,4 +53,4 @@
android:adjustViewBounds="true"
android:src="@drawable/ic_draggable_white"
tools:ignore="ContentDescription" />
-</android.support.v17.leanback.widget.GuidedActionItemContainer>
\ No newline at end of file
+</androidx.leanback.widget.GuidedActionItemContainer>
\ No newline at end of file
diff --git a/res/layout/program_guide_side_panel.xml b/res/layout/program_guide_side_panel.xml
index 466b7fa..9c04feb 100644
--- a/res/layout/program_guide_side_panel.xml
+++ b/res/layout/program_guide_side_panel.xml
@@ -32,7 +32,7 @@
android:elevation="@dimen/program_guide_side_panel_elevation"
android:background="@color/program_guide_side_panel_background">
- <android.support.v17.leanback.widget.SearchOrbView
+ <androidx.leanback.widget.SearchOrbView
android:id="@+id/program_guide_side_panel_search_orb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -41,7 +41,7 @@
android:nextFocusDown="@+id/program_guide_side_panel_grid_view"
android:visibility="gone" />
- <android.support.v17.leanback.widget.VerticalGridView
+ <androidx.leanback.widget.VerticalGridView
android:id="@id/program_guide_side_panel_grid_view"
android:layout_width="@dimen/program_guide_side_panel_item_width"
android:layout_height="match_parent"
diff --git a/res/values/arrays-custom.xml b/res/values/arrays-custom.xml
new file mode 100644
index 0000000..252d6f4
--- /dev/null
+++ b/res/values/arrays-custom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<!-- These are resources that are expected to be different for OEM apps -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Keep the title and description arrays in sync -->
+
+ <!-- Titles in the onboarding page. [CHAR LIMIT=100]-->
+ <string-array name="welcome_page_titles">
+ <item><xliff:g id="app_name">Live TV</xliff:g> </item>
+ <item>A simple way to discover content</item>
+ <item>Download apps, get more channels</item>
+ <item>Customize your channel line-up</item>
+ </string-array>
+
+
+ <!-- Descriptions in the onboarding page. [CHAR LIMIT=NONE] -->
+ <string-array name="welcome_page_descriptions">
+ <item>Watch content from your apps like watching channels on TV.</item>
+ <item>Browse content from your apps with a familiar guide and friendly interface,
+\njust like channels on TV.</item>
+ <item>Add more channels by installing apps that offer live channels.
+\nFind compatible apps in Google Play Store by using the link within the TV menu.</item>
+ <!-- Refer to @string/settings_channel_source_item_setup for "Channel sources" menu
+ and @string/options_item_settings for "Settings" menu. -->
+ <item>Set up your newly installed channel sources to customize your channel list.
+\nChoose the Channel sources within the Settings menu to get started.</item>
+ </string-array>
+</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 0604dd2..9ef028a 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -150,24 +150,4 @@
<item>Tech/Science</item>
</string-array>
- <!-- Titles in the onboarding page. -->
- <string-array name="welcome_page_titles">
- <item><xliff:g id="app_name">Live TV</xliff:g> </item>
- <item>A simple way to discover content</item>
- <item>Download apps, get more channels</item>
- <item>Customize your channel line-up</item>
- </string-array>
-
- <!-- Descriptions in the onboarding page. -->
- <string-array name="welcome_page_descriptions">
- <item>Watch content from your apps like watching channels on TV.</item>
- <item>Browse content from your apps with a familiar guide and friendly interface,
-\njust like channels on TV.</item>
- <item>Add more channels by installing apps that offer live channels.
-\nFind compatible apps in the online store by using the link within the TV menu.</item>
- <!-- Refer to @string/settings_channel_source_item_setup for "Channel sources" menu
- and @string/options_item_settings for "Settings" menu. -->
- <item>Set up your newly installed channel sources to customize your channel list.
-\nChoose the Channel sources within the Settings menu to get started.</item>
- </string-array>
</resources>
diff --git a/res/values/rating_system_strings.xml b/res/values/rating_system_strings.xml
index 45c48d8..9922cb1 100644
--- a/res/values/rating_system_strings.xml
+++ b/res/values/rating_system_strings.xml
@@ -16,9 +16,10 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Age based TV content rating strings used in DVB and ISDB.
- For more info, please see STD-B10 in http://www.dibeg.org/techp/aribstd/aribstd.html (ISDB)
- and Table 81 of DVB SI (EN 300 468 V1.14.1) in https://www.dvb.org/standards (DVB).-->
+ <!-- Age based TV content rating strings used in DVB, ISDB and DTMB.
+ For more info, please see STD-B10 in http://www.dibeg.org/techp/aribstd/aribstd.html (ISDB),
+ Table 81 of DVB SI (EN 300 468 V1.14.1) in https://www.dvb.org/standards (DVB)
+ and http://www.gb688.cn/bzgk/gb/newGbInfo?hcno=59E83CA701AEB4248E115BC043688FEC (DTMB).-->
<string name="description_age_4">Recommended for ages 4 and over.</string>
<string name="description_age_5">Recommended for ages 5 and over.</string>
<string name="description_age_6">Recommended for ages 6 and over.</string>
@@ -109,6 +110,7 @@
<!-- A TV content rating of DVB for adult [CHAR LIMIT=NONE] -->
<string name="description_es_dvb_x">Recommended for adults.</string>
+
<!-- TV content rating system strings for KR TV. These strings are from
http://www.law.go.kr/admRulLsInfoP.do?admRulSeq=2000000118507 but they are translated
from Korean to English. -->
@@ -159,4 +161,19 @@
<string name="description_us_mv_pg13">Parents strongly cautioned. Some material may be inappropriate for pre-teenagers.</string>
<string name="description_us_mv_r">Restricted, Contains some adult material. Parents are urged to learn more about the film before taking their young children with them.</string>
<string name="description_us_mv_nc17">No one 17 and under admitted. Clearly adult. Children are not admitted.</string>
+
+ <!-- TV content rating system strings for NZ TV. These strings are from
+ https://bsa.govt.nz/images/03_BSA_FREE-TO-AIR-TV_CLASSIFICATIONS_DRAFT.pdf -->
+ <string name="description_nz_fta_tv_g" translatable="false">Programmes which exclude material likely to be unsuitable for children. Programmes may not necessarily be designed for child viewers but should not contain material likely to alarm or distress them.</string>
+ <string name="description_nz_fta_tv_pgr" translatable="false">Programmes containing material more suited for mature audiences but not necessarily unsuitable for child viewers when subject to the guidance of a parent or an adult.</string>
+ <string name="description_nz_fta_tv_ao" translatable="false">Programmes containing adult themes and directed primarily at mature audiences.</string>
+
+ <!-- TV content rating system strings for TH TV. These strings are from
+ https://broadcast.nbtc.go.th/home/ -->
+ <string name="description_th_tv_4" translatable="false">Suitable for audiences 3 to 5 years of age.</string>
+ <string name="description_th_tv_6" translatable="false">Suitable for audiences 6 to 12 years of age.</string>
+ <string name="description_th_tv_10" translatable="false">Suitable for all audiences.</string>
+ <string name="description_th_tv_13" translatable="false">Parental guidance suggested for viewers age below 13.</string>
+ <string name="description_th_tv_18" translatable="false">Parental guidance suggested for viewers age below 18.</string>
+ <string name="description_th_tv_19" translatable="false">Not suitable for children and teenagers.</string>
</resources>
diff --git a/res/values/strings-custom.xml b/res/values/strings-custom.xml
index 22f7331..d9a7a26 100644
--- a/res/values/strings-custom.xml
+++ b/res/values/strings-custom.xml
@@ -14,9 +14,21 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<resources>
+
+
+<!-- These are resources that are expected to be different for OEM apps -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Name of application [CHAR LIMIT=NONE] -->
<string name="app_name" translatable="false">Live TV</string>
+ <!-- Description for channel sources screen in onboarding. [CHAR LIMIT=NONE] -->
+ <string name="setup_sources_description">Live TV combines the experience of traditional TV channels with streaming channels provided by apps.
+\n\nGet started by setting up the channel sources already installed. Or browse Google Play Store for more apps that offer live channels.</string>
+ <!-- Description for channel sources screen in onboarding. [CHAR LIMIT=NONE] -->
+ <string name="setup_sources_description2"
+ meaning="Live TV version of setup_sources_description2"
+ ><xliff:g id="app_name">Live TV</xliff:g> combines the experience of traditional TV channels with streaming channels provided by apps.
+\n\nGet started by setting up the channel sources already installed. Or browse Google Play Store for more apps that offer live channels.</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3682475..06694a9 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -28,16 +28,16 @@
<!-- Title of an application permission, listed so the user can choose
whether they want to allow the application to do this. -->
- <string name="permlab_receiveInputEvent" translatable="false">receive input events from <xliff:g id="app_name">Live TV</xliff:g> app</string>
+ <string name="permlab_receiveInputEvent" translatable="false">receive input events from <xliff:g id="app_name">Live TV</xliff:g> app</string>
<!-- Description of an application permission, listed so the user can choose
whether they want to allow the application to do this. -->
- <string name="permdesc_receiveInputEvent" translatable="false">Allows the app to receive input events from <xliff:g id="app_name">Live TV</xliff:g> app</string>
+ <string name="permdesc_receiveInputEvent" translatable="false">Allows the app to receive input events from <xliff:g id="app_name">Live TV</xliff:g> app</string>
<!-- Title of an application permission, listed so the user can choose
whether they want to allow the application to do this. -->
- <string name="permlab_customizeTvApp" translatable="false">customize <xliff:g id="app_name">Live TV</xliff:g> app</string>
+ <string name="permlab_customizeTvApp" translatable="false">customize <xliff:g id="app_name">Live TV</xliff:g> app</string>
<!-- Description of an application permission, listed so the user can choose
whether they want to allow the application to do this. -->
- <string name="permdesc_customizeTvApp" translatable="false">Allows the app to customize <xliff:g id="app_name">Live TV</xliff:g> app</string>
+ <string name="permdesc_customizeTvApp" translatable="false">Allows the app to customize <xliff:g id="app_name">Live TV</xliff:g> app</string>
<!-- Program information, mainly used for channel banner and program guide. -->
<eat-comment />
@@ -388,13 +388,13 @@
<!-- Description on the locked screen with the rating when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
<string name="tvview_content_locked_format">This program is rated <xliff:g id="rating" example="TV_MA">%1$s</xliff:g>.\nTo watch this program, press Right and enter your PIN.</string>
<!-- Description on the locked screen when current channel is locked by parental control. [CHAR LIMIT=NONE] -->
- <string name="tvview_channel_locked_no_permission">To watch this channel, use the default Live TV app.</string>
+ <string name="tvview_channel_locked_no_permission">To watch this channel, use the default TV app.</string>
<!-- Description on the locked screen when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
- <string name="tvview_content_locked_no_permission">To watch this program, use the default Live TV app.</string>
+ <string name="tvview_content_locked_no_permission">To watch this program, use the default TV app.</string>
<!-- Description on the locked screen when the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
- <string name="tvview_content_locked_unrated_no_permission">This program is unrated.\nTo watch this program, use the default Live TV app.</string>
+ <string name="tvview_content_locked_unrated_no_permission">This program is unrated.\nTo watch this program, use the default TV app.</string>
<!-- Description on the locked screen with the rating when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
- <string name="tvview_content_locked_format_no_permission">This program is rated <xliff:g id="rating" example="TV_MA">%1$s</xliff:g>.\nTo watch this program, use the default Live TV app.</string>
+ <string name="tvview_content_locked_format_no_permission">This program is rated <xliff:g id="rating" example="TV_MA">%1$s</xliff:g>.\nTo watch this program, use the default TV app.</string>
<!-- Description on the locked screen when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
<string name="shrunken_tvview_content_locked">Program is blocked</string>
@@ -479,7 +479,7 @@
the source video through to the display and don't provide ability to tune to a specific
channel unless the user directly controls the external source device (e.g. game console,
DVD player, settop box, etc) that is connected to the TV. [CHAR LIMIT=NONE] -->
- <string name="msg_not_passthrough_input">Tuner type not suitable. Please launch <xliff:g id="app_name">Live TV</xliff:g> app for tuner type TV input.</string>
+ <string name="msg_not_passthrough_input">Tuner type not suitable. Please launch <xliff:g id="app_name">Live TV</xliff:g> app for tuner type TV input.</string>
<!-- Error message when tune is failed. [CHAR LIMIT=NONE] -->
<string name="msg_tune_failed">Tune failed</string>
<!-- Error message when the user attempts an action (select TIS setup-activity, app-link,
@@ -496,7 +496,7 @@
(commands for external device) [CHAR LIMIT=NONE] -->
<string name="msg_back_key_guide">BACK key is for connected device. Press HOME button to exit.</string>
<!-- Error message when a user denied to grant READ_TV_LISTING permission. [CHAR LIMIT=NONE] -->
- <string name="msg_read_tv_listing_permission_denied"><xliff:g id="app_name">Live TV</xliff:g> needs permission to read the TV listings.</string>
+ <string name="msg_read_tv_listing_permission_denied"><xliff:g id="app_name">Live TV</xliff:g> needs permission to read the TV listings.</string>
<!-- Strings for debug or not to be shown to users -->
<eat-comment />
@@ -518,17 +518,17 @@
<!-- Title of Recently watched dialog. It is used for debug purpose. -->
<string name="recently_watched" translatable="false">Recently watched</string>
- <!-- Title of DVR history dialog. -->
+ <!-- Title of DVR history dialog. [CHAR LIMIT=50] -->
<string name="dvr_history_dialog_title" translatable="false">DVR history</string>
- <!-- Display name of DVR recording service's notification channel. -->
- <string name="dvr_notification_channel_name" translatable="false"><xliff:g id="app_name">Live TV</xliff:g> DVR</string>
- <!-- Content title of DVR recording service's notification. -->
- <string name="dvr_notification_content_title" translatable="false"><xliff:g id="app_name">Live TV</xliff:g> DVR</string>
- <!-- Content text of DVR recording service's notification during recording. -->
- <string name="dvr_notification_content_text_recording" translatable="false"><xliff:g id="app_name">Live TV</xliff:g> are recording.</string>
- <!-- Content text of DVR recording service's notification during updating schedules. -->
- <string name="dvr_notification_content_text_loading" translatable="false"><xliff:g id="app_name">Live TV</xliff:g> are updating recording schedules.</string>
+ <!-- Display name of DVR recording service's notification channel. [CHAR LIMIT=50] -->
+ <string name="dvr_notification_channel_name" ><xliff:g id="app_name">Live TV</xliff:g> DVR</string>
+ <!-- Content title of DVR recording service's notification. [CHAR LIMIT=50] -->
+ <string name="dvr_notification_content_title" ><xliff:g id="app_name">Live TV</xliff:g> DVR</string>
+ <!-- Content text of DVR recording service's notification during recording. [CHAR LIMIT=NONE] -->
+ <string name="dvr_notification_content_text_recording" ><xliff:g id="app_name">Live TV</xliff:g> are recording.</string>
+ <!-- Content text of DVR recording service's notification during updating schedules. [CHAR LIMIT=NONE] -->
+ <string name="dvr_notification_content_text_loading" ><xliff:g id="app_name">Live TV</xliff:g> are updating recording schedules.</string>
<!-- Default content title of tuner installing notifications. -->
<string name="tuner_install_notification_content_title" translatable="false">Install <xliff:g id="tuner_package" example="Tuner package">%s</xliff:g></string>
@@ -546,9 +546,6 @@
<eat-comment />
<!-- Text for the channel sources screen in onboarding. -->
<string name="setup_sources_text">Set up your sources</string>
- <!-- Description for channel sources screen in onboarding. -->
- <string name="setup_sources_description">Live channels combines the experience of traditional TV channels with streaming channels provided by apps.
-\n\nGet started by setting up the channel sources already installed. Or browse the online store for more apps that offer live channels.</string>
<!-- Menu item label to start DVR manager UI. -->
<string name="channels_item_dvr">Recordings & schedules</string>
@@ -693,8 +690,8 @@
<item quantity="one">%1$d of %2$d episode is deleted</item>
<item quantity="other">%1$d of %2$d episodes are deleted</item>
</plurals>
- <!-- Title of DVR series settings -->
- <string name="dvr_series_settings_title" translatable="false">Recording settings</string>
+ <!-- Title of screen with settings for the recording of a TV Program or Series. [CHAR LIMIT=50] -->
+ <string name="dvr_series_settings_title">Recording settings</string>
<!-- Item label to change priority of TV series for DVR -->
<string name="dvr_series_settings_priority">Priority</string>
<!-- Item description when the current series has the height proirty among scheduled
@@ -722,10 +719,13 @@
<!-- DVR epg strings -->
<eat-comment />
- <string name="dvr_action_delete_schedule" translatable="false">Delete schedule</string>
- <string name="dvr_action_record_program" translatable="false">Record program</string>
- <!-- The action to forget DVR storage which is missing currently. invoke android internal storage settings activity. -->
- <string name="dvr_action_error_storage_settings" translatable="false">Open storage settings</string>
+ <!-- Button text that deletes scheduled future recordings of a TV Program or Series. [CHAR LIMIT=50] -->
+ <string name="dvr_action_delete_schedule">Delete schedule</string>
+ <!-- Button text that records the selected program. [CHAR LIMIT=50] -->
+ <string name="dvr_action_record_program">Record program</string>
+ <!-- Button text that invokes android internal storage settings activity.
+ [CHAR LIMIT=50] -->
+ <string name="dvr_action_error_storage_settings">Open storage settings</string>
<!-- The action to stop recording. [CHAR LIMIT=10] -->
<string name="dvr_action_stop">Stop</string>
<!-- The action to open the activity which shows all the schedules.[CHAR LIMIT=32] -->
@@ -767,8 +767,10 @@
<!-- Dvr label in epg to indicate the recording of the program has been failed. [CHAR LIMIT=30] -->
<string name="dvr_epg_program_recording_failed">Recording failed</string>
<string name="dvr_epg_program_icon_text" translatable="false">DVR</string>
- <string name="dvr_epg_channel_watch_conflict_dialog_title" translatable="false">Upcoming schedules</string>
- <string name="dvr_epg_channel_watch_conflict_dialog_description" translatable="false">The programs will not be recorded if you keep watching this channel. Cancel the recordings, or current channel will be blocked when the recording starts.</string>
+ <!-- Title of a dialog box displayed when a previously scheduled recording requires an action.[CHAR LIMIT=50] -->
+ <string name="dvr_epg_channel_watch_conflict_dialog_title">Upcoming schedules</string>
+ <!-- Description in a dialog box displayed when a previously scheduled recording requires an action. [CHAR LIMIT=NONE] -->
+ <string name="dvr_epg_channel_watch_conflict_dialog_description">The program will not be recorded if you keep watching this channel. Cancel the recording, or the current channel will be blocked when the recording starts.</string>
<!-- A popup message which informs that Live TV is reading program information. -->
<string name="dvr_series_progress_message_reading_programs">Reading programs</string>
<!-- Dialog action which let user view the recent recordings. -->
@@ -798,10 +800,10 @@
<string name="dvr_error_no_free_space_description">This program will not be recorded because there is not enough storage. Try deleting some existing recordings.</string>
<!-- Dialog title which will be shown when the current DVR storage is not accessible. -->
<string name="dvr_error_missing_storage_title">Missing storage</string>
- <!-- Dialog description which will be shown when the current DVR storage is not accessible. -->
- <string name="dvr_error_missing_storage_description" translatable="false">Some of the storage used for recording is missing. Please connect the external drive you used before to re-enable recording. Alternately, you can forget the storage in the storage settings, if it\'s no longer available.</string>
- <!-- The recording being requested to play is not existent in storage. It may be deleted. -->
- <string name="dvr_toast_recording_deleted" translatable="false">The recording seems to be deleted.</string>
+ <!-- Dialog description which will be shown when the current DVR storage is not accessible. [CHAR LIMIT=NONE] -->
+ <string name="dvr_error_missing_storage_description">Some of the storage used for recording is missing. Please connect the external drive you used before to re-enable recording. Alternately, you can forget the storage in the storage settings, if it\'s no longer available.</string>
+ <!-- A toast message displad to the user when the recording being requested to play is not found in storage. [CHAR LIMIT=NONE] -->
+ <string name="dvr_toast_recording_deleted">Recording not found.</string>
<!-- DVR half sized dialogs -->
<eat-comment />
@@ -908,7 +910,8 @@
<!-- DVR channel banner strings -->
<eat-comment />
- <string name="dvr_recording_till_format" translatable="false">Recording till <xliff:g id="recordingEndTime" example="9:00pm">%1$s</xliff:g></string>
+ <!-- Text desplayed on screen to show that the current program is being recorded until the time listed. [CHAR LIMIT=30] -->
+ <string name="dvr_recording_till_format">Recording till <xliff:g id="recordingEndTime" example="9:00pm">%1$s</xliff:g></string>
<!-- DVR schedule list strings -->
<eat-comment/>
diff --git a/res/xml/tv_content_rating_systems.xml b/res/xml/tv_content_rating_systems.xml
index aad9d61..361393f 100644
--- a/res/xml/tv_content_rating_systems.xml
+++ b/res/xml/tv_content_rating_systems.xml
@@ -286,6 +286,170 @@
</rating-order>
</rating-system-definition>
+ <!-- TV content rating system for DTMB. See http://www.gb688.cn/bzgk/gb/newGbInfo?hcno=59E83CA701AEB4248E115BC043688FEC -->
+ <rating-system-definition android:name="DTMB"
+ android:country="CN">
+ <rating-definition android:name="DTMB_4"
+ android:title="4"
+ android:description="@string/description_age_4"
+ android:contentAgeHint="4" />
+ <rating-definition android:name="DTMB_5"
+ android:title="5"
+ android:description="@string/description_age_5"
+ android:contentAgeHint="5" />
+ <rating-definition android:name="DTMB_6"
+ android:title="6"
+ android:description="@string/description_age_6"
+ android:contentAgeHint="6" />
+ <rating-definition android:name="DTMB_7"
+ android:title="7"
+ android:description="@string/description_age_7"
+ android:contentAgeHint="7" />
+ <rating-definition android:name="DTMB_8"
+ android:title="8"
+ android:description="@string/description_age_8"
+ android:contentAgeHint="8" />
+ <rating-definition android:name="DTMB_9"
+ android:title="9"
+ android:description="@string/description_age_9"
+ android:contentAgeHint="9" />
+ <rating-definition android:name="DTMB_10"
+ android:title="10"
+ android:description="@string/description_age_10"
+ android:contentAgeHint="10" />
+ <rating-definition android:name="DTMB_11"
+ android:title="11"
+ android:description="@string/description_age_11"
+ android:contentAgeHint="11" />
+ <rating-definition android:name="DTMB_12"
+ android:title="12"
+ android:description="@string/description_age_12"
+ android:contentAgeHint="12" />
+ <rating-definition android:name="DTMB_13"
+ android:title="13"
+ android:description="@string/description_age_13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="DTMB_14"
+ android:title="14"
+ android:description="@string/description_age_14"
+ android:contentAgeHint="14" />
+ <rating-definition android:name="DTMB_15"
+ android:title="15"
+ android:description="@string/description_age_15"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="DTMB_16"
+ android:title="16"
+ android:description="@string/description_age_16"
+ android:contentAgeHint="16" />
+ <rating-definition android:name="DTMB_17"
+ android:title="17"
+ android:description="@string/description_age_17"
+ android:contentAgeHint="17" />
+ <rating-definition android:name="DTMB_18"
+ android:title="18"
+ android:description="@string/description_age_18"
+ android:contentAgeHint="18" />
+ <rating-order>
+ <rating android:name="DTMB_4" />
+ <rating android:name="DTMB_5" />
+ <rating android:name="DTMB_6" />
+ <rating android:name="DTMB_7" />
+ <rating android:name="DTMB_8" />
+ <rating android:name="DTMB_9" />
+ <rating android:name="DTMB_10" />
+ <rating android:name="DTMB_11" />
+ <rating android:name="DTMB_12" />
+ <rating android:name="DTMB_13" />
+ <rating android:name="DTMB_14" />
+ <rating android:name="DTMB_15" />
+ <rating android:name="DTMB_16" />
+ <rating android:name="DTMB_17" />
+ <rating android:name="DTMB_18" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for DTMB. See http://www.gb688.cn/bzgk/gb/newGbInfo?hcno=59E83CA701AEB4248E115BC043688FEC -->
+ <rating-system-definition android:name="DTMB"
+ android:country="CN">
+ <rating-definition android:name="DTMB_4"
+ android:title="4"
+ android:description="@string/description_age_4"
+ android:contentAgeHint="4" />
+ <rating-definition android:name="DTMB_5"
+ android:title="5"
+ android:description="@string/description_age_5"
+ android:contentAgeHint="5" />
+ <rating-definition android:name="DTMB_6"
+ android:title="6"
+ android:description="@string/description_age_6"
+ android:contentAgeHint="6" />
+ <rating-definition android:name="DTMB_7"
+ android:title="7"
+ android:description="@string/description_age_7"
+ android:contentAgeHint="7" />
+ <rating-definition android:name="DTMB_8"
+ android:title="8"
+ android:description="@string/description_age_8"
+ android:contentAgeHint="8" />
+ <rating-definition android:name="DTMB_9"
+ android:title="9"
+ android:description="@string/description_age_9"
+ android:contentAgeHint="9" />
+ <rating-definition android:name="DTMB_10"
+ android:title="10"
+ android:description="@string/description_age_10"
+ android:contentAgeHint="10" />
+ <rating-definition android:name="DTMB_11"
+ android:title="11"
+ android:description="@string/description_age_11"
+ android:contentAgeHint="11" />
+ <rating-definition android:name="DTMB_12"
+ android:title="12"
+ android:description="@string/description_age_12"
+ android:contentAgeHint="12" />
+ <rating-definition android:name="DTMB_13"
+ android:title="13"
+ android:description="@string/description_age_13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="DTMB_14"
+ android:title="14"
+ android:description="@string/description_age_14"
+ android:contentAgeHint="14" />
+ <rating-definition android:name="DTMB_15"
+ android:title="15"
+ android:description="@string/description_age_15"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="DTMB_16"
+ android:title="16"
+ android:description="@string/description_age_16"
+ android:contentAgeHint="16" />
+ <rating-definition android:name="DTMB_17"
+ android:title="17"
+ android:description="@string/description_age_17"
+ android:contentAgeHint="17" />
+ <rating-definition android:name="DTMB_18"
+ android:title="18"
+ android:description="@string/description_age_18"
+ android:contentAgeHint="18" />
+ <rating-order>
+ <rating android:name="DTMB_4" />
+ <rating android:name="DTMB_5" />
+ <rating android:name="DTMB_6" />
+ <rating android:name="DTMB_7" />
+ <rating android:name="DTMB_8" />
+ <rating android:name="DTMB_9" />
+ <rating android:name="DTMB_10" />
+ <rating android:name="DTMB_11" />
+ <rating android:name="DTMB_12" />
+ <rating android:name="DTMB_13" />
+ <rating android:name="DTMB_14" />
+ <rating android:name="DTMB_15" />
+ <rating android:name="DTMB_16" />
+ <rating android:name="DTMB_17" />
+ <rating android:name="DTMB_18" />
+ </rating-order>
+ </rating-system-definition>
+
<!-- TV content rating system for DVB. See Table 81 of DVB SI (EN 300 468 V1.14.1) in https://www.dvb.org/standards -->
<rating-system-definition android:name="DVB"
android:country="AM, BG, CH, DE, DK, FI, GR, HU, ID, IE, IL, IS, MY, NL, NZ, PL, PT, RO, RU, RS, SI, TH, TR, TW, UA">
@@ -676,6 +840,28 @@
</rating-order>
</rating-system-definition>
+ <!-- TV content rating system for NZ. See http://www.freeviewnz.tv/ -->
+ <rating-system-definition android:name="NZ_TV"
+ android:country="NZ">
+ <rating-definition android:name="NZ_TV_G"
+ android:title="G"
+ android:description="@string/description_nz_fta_tv_g"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="NZ_TV_PGR"
+ android:title="PGR"
+ android:description="@string/description_nz_fta_tv_pgr"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="NZ_TV_AO"
+ android:title="AO"
+ android:description="@string/description_nz_fta_tv_ao"
+ android:contentAgeHint="0" />
+ <rating-order>
+ <rating android:name="NZ_TV_G" />
+ <rating android:name="NZ_TV_PGR" />
+ <rating android:name="NZ_TV_AO" />
+ </rating-order>
+ </rating-system-definition>
+
<!-- TV content rating system for SG. See http://www.mda.gov.sg/RegulationsAndLicensing/ContentStandardsAndClassification/FilmsAndVideos/Pages/default.aspx -->
<rating-system-definition android:name="SG_TV"
android:country="SG">
@@ -713,6 +899,43 @@
</rating-order>
</rating-system-definition>
+ <!-- TV content rating system for TH. See https://broadcast.nbtc.go.th/home/ -->
+ <rating-system-definition android:name="TH_TV"
+ android:country="TH">
+ <rating-definition android:name="TH_TV_4"
+ android:title="4"
+ android:description="@string/description_th_tv_4"
+ android:contentAgeHint="4" />
+ <rating-definition android:name="TH_TV_6"
+ android:title="6"
+ android:description="@string/description_th_tv_6"
+ android:contentAgeHint="6" />
+ <rating-definition android:name="TH_TV_10"
+ android:title="10"
+ android:description="@string/description_th_tv_10"
+ android:contentAgeHint="10" />
+ <rating-definition android:name="TH_TV_13"
+ android:title="13"
+ android:description="@string/description_th_tv_13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="TH_TV_18"
+ android:title="18"
+ android:description="@string/description_th_tv_18"
+ android:contentAgeHint="18" />
+ <rating-definition android:name="TH_TV_19"
+ android:title="19"
+ android:description="@string/description_th_tv_19"
+ android:contentAgeHint="19" />
+ <rating-order>
+ <rating android:name="TH_TV_4" />
+ <rating android:name="TH_TV_6" />
+ <rating android:name="TH_TV_10" />
+ <rating android:name="TH_TV_13" />
+ <rating android:name="TH_TV_18" />
+ <rating android:name="TH_TV_19" />
+ </rating-order>
+ </rating-system-definition>
+
<!-- TV content rating system for US. See http://www.tvguidelines.org/ratings.htm -->
<rating-system-definition android:name="US_TV"
android:country="US">
diff --git a/settings.gradle b/settings.gradle
index 6d5cb54..5916cb4 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -23,3 +23,5 @@
include ':tuner'
include ':SampleDvbTuner'
project(":SampleDvbTuner").projectDir = file("tuner/SampleDvbTuner")
+include ':SampleNetworkTuner'
+project(":SampleNetworkTuner").projectDir = file("tuner/SampleNetworkTuner")
diff --git a/src/com/android/tv/LauncherActivity.java b/src/com/android/tv/LauncherActivity.java
index 679d612..ccc5600 100644
--- a/src/com/android/tv/LauncherActivity.java
+++ b/src/com/android/tv/LauncherActivity.java
@@ -27,10 +27,10 @@
* An activity to launch a new activity.
*
* <p>In the case when {@link MainActivity} starts a new activity using {@link
- * Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is
- * terminated if the new activity crashes. That's because the {@link android.app.ActivityManager}
- * terminates the activity which is just below the crashed activity in the activity stack. To avoid
- * this, we need to locate an additional activity between these activities in the activity stack.
+ * Activity#startActivity} or {@link Activity#startActivityForResult}, TV app is terminated if the
+ * new activity crashes. That's because the {@link android.app.ActivityManager} terminates the
+ * activity which is just below the crashed activity in the activity stack. To avoid this, we need
+ * to locate an additional activity between these activities in the activity stack.
*/
public class LauncherActivity extends Activity {
private static final String TAG = "LauncherActivity";
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index b4cf71d..141ce82 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -44,6 +44,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
+import android.provider.BaseColumns;
import android.provider.Settings;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
@@ -65,6 +66,7 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.Toast;
+
import com.android.tv.MainActivity.MySingletons;
import com.android.tv.analytics.SendChannelStatusRunnable;
import com.android.tv.analytics.SendConfigInfoRunnable;
@@ -78,6 +80,7 @@
import com.android.tv.common.TvContentRatingCache;
import com.android.tv.common.WeakHandler;
import com.android.tv.common.compat.TvInputInfoCompat;
+import com.android.tv.common.dev.DeveloperPreferences;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.memory.MemoryManageable;
import com.android.tv.common.singletons.HasSingletons;
@@ -91,11 +94,13 @@
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelImpl;
import com.android.tv.data.OnCurrentProgramUpdatedListener;
-import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.StreamInfo;
import com.android.tv.data.WatchedHistoryManager;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+import com.android.tv.data.epg.EpgFetcher;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
@@ -106,11 +111,12 @@
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.features.TvFeatures;
+import com.android.tv.guide.ProgramItemView;
import com.android.tv.menu.Menu;
import com.android.tv.onboarding.OnboardingActivity;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.parental.ParentalControlSettings;
-import com.android.tv.perf.PerformanceMonitorManagerFactory;
+import com.android.tv.perf.StartupMeasureFactory;
import com.android.tv.receiver.AudioCapabilitiesReceiver;
import com.android.tv.recommendation.ChannelPreviewUpdater;
import com.android.tv.recommendation.NotificationService;
@@ -126,6 +132,7 @@
import com.android.tv.ui.TunableTvView.BlockScreenType;
import com.android.tv.ui.TunableTvView.OnTuneListener;
import com.android.tv.ui.TvOverlayManager;
+import com.android.tv.ui.TvOverlayManagerFactory;
import com.android.tv.ui.TvViewUiManager;
import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
import com.android.tv.ui.sidepanel.CustomizeChannelListFragment;
@@ -135,6 +142,7 @@
import com.android.tv.ui.sidepanel.SettingsFragment;
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment;
+import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.AsyncDbTask.DbExecutor;
import com.android.tv.util.CaptionSettings;
@@ -150,9 +158,17 @@
import com.android.tv.util.images.ImageCache;
import com.google.common.base.Optional;
+
import dagger.android.AndroidInjection;
+import dagger.android.AndroidInjector;
import dagger.android.ContributesAndroidInjector;
+import dagger.android.DispatchingAndroidInjector;
+import dagger.android.HasAndroidInjector;
+
import com.android.tv.common.flags.BackendKnobsFlags;
+import com.android.tv.common.flags.LegacyFlags;
+import com.android.tv.common.flags.StartupFlags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
@@ -163,15 +179,17 @@
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+
import javax.inject.Inject;
import javax.inject.Provider;
-/** The main activity for the Live TV app. */
+/** The main activity for the TV app. */
public class MainActivity extends Activity
implements OnActionClickListener,
OnPinCheckedListener,
ChannelChanger,
- HasSingletons<MySingletons> {
+ HasSingletons<MySingletons>,
+ HasAndroidInjector {
private static final String TAG = "MainActivity";
private static final boolean DEBUG = false;
private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
@@ -258,10 +276,11 @@
private static final long START_UP_TIMER_RESET_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(3);
{
- PerformanceMonitorManagerFactory.create().getStartupMeasure().onActivityInit();
+ StartupMeasureFactory.create().onActivityInit();
}
private final MySingletonsImpl mMySingletons = new MySingletonsImpl();
+ @Inject DispatchingAndroidInjector<Object> mAndroidInjector;
@Inject @DbExecutor Executor mDbExecutor;
private AccessibilityManager mAccessibilityManager;
@@ -278,8 +297,12 @@
private DvrManager mDvrManager;
private ConflictChecker mDvrConflictChecker;
@Inject BackendKnobsFlags mBackendKnobs;
+ @Inject LegacyFlags mLegacyFlags;
+ @Inject StartupFlags mStartupFlags;
@Inject SetupUtils mSetupUtils;
@Inject Optional<BuiltInTunerManager> mOptionalBuiltInTunerManager;
+ @Inject AccountHelper mAccountHelper;
+ @Inject EpgFetcher mEpgFetcher;
@VisibleForTesting protected TunableTvView mTvView;
private View mContentView;
@@ -314,6 +337,7 @@
private boolean mIsFilmModeSet;
private float mDefaultRefreshRate;
+ @Inject TvOverlayManagerFactory mOverlayFactory;
private TvOverlayManager mOverlayManager;
// mIsCurrentChannelUnblockedByUser and mWasChannelUnblockedBeforeShrunkenByUser are used for
@@ -477,7 +501,6 @@
AndroidInjection.inject(this);
mAccessibilityManager =
(AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
- TvSingletons tvSingletons = TvSingletons.getSingletons(this);
DurationTimer startUpDebugTimer = Debug.getTimer(Debug.TAG_START_UP_TIMER);
if (!startUpDebugTimer.isStarted()
|| startUpDebugTimer.getDuration() > START_UP_TIMER_RESET_THRESHOLD_MS) {
@@ -496,6 +519,7 @@
finishAndRemoveTask();
return;
}
+ mAccountHelper.init();
TvSingletons tvApplication = (TvSingletons) getApplication();
// In API 23, TvContract.isChannelUriForPassthroughInput is hidden.
@@ -514,8 +538,8 @@
return;
}
setContentView(R.layout.activity_tv);
- mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view);
- mTvView.initialize(mProgramDataManager, mTvInputManagerHelper);
+ mTvView = findViewById(R.id.main_tunable_tv_view);
+ mTvView.initialize(mProgramDataManager, mTvInputManagerHelper, mLegacyFlags);
mTvView.setOnUnhandledInputEventListener(
new OnUnhandledInputEventListener() {
@Override
@@ -545,12 +569,13 @@
String inputId = Utils.getLastWatchedTunerInputId(this);
if (!isPassthroughInput
&& inputId != null
+ && !mStartupFlags.warmupInputidBlacklist().getElementList().contains(inputId)
&& channelId != Channel.INVALID_ID) {
mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId));
}
tvApplication.getMainActivityWrapper().onMainActivityCreated(this);
- if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
+ if (BuildConfig.ENG && DeveloperPreferences.ALLOW_STRICT_MODE.get(this)) {
Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
}
mTracker = tvApplication.getTracker();
@@ -612,13 +637,10 @@
}
mTvViewUiManager =
new TvViewUiManager(
- this,
- mTvView,
- (FrameLayout) findViewById(android.R.id.content),
- mTvOptionsManager);
+ this, mTvView, findViewById(android.R.id.content), mTvOptionsManager);
mContentView = findViewById(android.R.id.content);
- ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container);
+ ViewGroup sceneContainer = findViewById(R.id.scene_container);
ChannelBannerView channelBannerView =
(ChannelBannerView)
getLayoutInflater().inflate(R.layout.channel_banner, sceneContainer, false);
@@ -671,7 +693,7 @@
});
mSearchFragment = new ProgramGuideSearchFragment();
mOverlayManager =
- new TvOverlayManager(
+ mOverlayFactory.create(
this,
mChannelTuner,
mTvView,
@@ -709,8 +731,6 @@
SendChannelStatusRunnable.startChannelStatusRecurringRunner(
this, mTracker, mChannelDataManager);
- // To avoid not updating Rating systems when changing language.
- mTvInputManagerHelper.getContentRatingsManager().update();
if (CommonFeatures.DVR.isEnabled(this)
&& TvFeatures.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) {
mDvrConflictChecker = new ConflictChecker(this);
@@ -742,7 +762,7 @@
mChannelDataManager.reload();
mProgramDataManager.reload();
- // Restart live channels.
+ // Restart TV app.
Intent intent = getIntent();
finish();
startActivity(intent);
@@ -830,7 +850,7 @@
.getTunerInputController()
.executeNetworkTunerDiscoveryAsyncTask(this);
}
- TvSingletons.getSingletons(this).getEpgFetcher().fetchImmediatelyIfNeeded();
+ mEpgFetcher.fetchImmediatelyIfNeeded();
}
@Override
@@ -1127,8 +1147,8 @@
Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show();
return;
}
- // Even though other app can handle the intent, the setup launched by Live channels
- // should go through Live channels SetupPassthroughActivity.
+ // Even though other app can handle the intent, the setup launched by TV app
+ // should go through TV app SetupPassthroughActivity.
intent.setComponent(new ComponentName(this, SetupPassthroughActivity.class));
try {
// Now we know that the user intends to set up this input. Grant permission for writing
@@ -1398,7 +1418,9 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
+ if (DeveloperPreferences.LOG_KEYEVENT.get(this)) {
+ Log.d(TAG, "dispatchKeyEvent(" + event + ")");
+ }
// If an activity is closed on a back key down event, back key down events with none zero
// repeat count or a back key up event can be happened without the first back key down
// event which should be ignored in this activity.
@@ -1503,7 +1525,7 @@
new AsyncQueryProgramTask(
mDbExecutor,
programUriFromIntent,
- Program.PROJECTION,
+ ProgramImpl.PROJECTION,
null,
null,
null,
@@ -1534,7 +1556,7 @@
String inputId = mInitChannelUri.getQueryParameter("input");
long channelId = Utils.getLastWatchedChannelIdForInput(this, inputId);
if (channelId == Channel.INVALID_ID) {
- String[] projection = {Channels._ID};
+ String[] projection = {BaseColumns._ID};
long time = System.currentTimeMillis();
try (Cursor cursor =
getContentResolver().query(uri, projection, null, null, null)) {
@@ -1582,7 +1604,7 @@
protected Program onQuery(Cursor c) {
Program program = null;
if (c != null && c.moveToNext()) {
- program = Program.fromCursor(c);
+ program = ProgramImpl.fromCursor(c);
}
return program;
}
@@ -1598,7 +1620,7 @@
Intent intent = new Intent(MainActivity.this, DetailsActivity.class);
intent.putExtra(DetailsActivity.CHANNEL_ID, mChannelIdFromIntent);
intent.putExtra(DetailsActivity.DETAILS_VIEW_TYPE, DetailsActivity.PROGRAM_VIEW);
- intent.putExtra(DetailsActivity.PROGRAM, program);
+ intent.putExtra(DetailsActivity.PROGRAM, program.toParcelable());
intent.putExtra(DetailsActivity.INPUT_ID, channel.getInputId());
startActivity(intent);
}
@@ -2077,7 +2099,7 @@
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (SystemProperties.LOG_KEYEVENT.getValue()) {
+ if (DeveloperPreferences.LOG_KEYEVENT.get(this)) {
Log.d(TAG, "onKeyDown(" + keyCode + ", " + event + ")");
}
switch (mOverlayManager.onKeyDown(keyCode, event)) {
@@ -2167,7 +2189,7 @@
* W debug: toggle screen size
* V KEYCODE_MEDIA_RECORD debug: record the current channel for 30 sec
*/
- if (SystemProperties.LOG_KEYEVENT.getValue()) {
+ if (DeveloperPreferences.LOG_KEYEVENT.get(this)) {
Log.d(TAG, "onKeyUp(" + keyCode + ", " + event + ")");
}
// If we are in the middle of channel change, finish it before showing overlays.
@@ -2265,7 +2287,7 @@
// Channel change is already done in the head of this method.
return true;
case KeyEvent.KEYCODE_S:
- if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
+ if (!DeveloperPreferences.USE_DEBUG_KEYS.get(this)) {
break;
}
// fall through.
@@ -2273,7 +2295,7 @@
mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment());
return true;
case KeyEvent.KEYCODE_A:
- if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
+ if (!DeveloperPreferences.USE_DEBUG_KEYS.get(this)) {
break;
}
// fall through.
@@ -2339,7 +2361,7 @@
// in case that TV isn't showing properly (e.g. no browsable channel)
return true;
}
- if (SystemProperties.USE_DEBUG_KEYS.getValue() || BuildConfig.ENG) {
+ if (DeveloperPreferences.USE_DEBUG_KEYS.get(this) || BuildConfig.ENG) {
switch (keyCode) {
case KeyEvent.KEYCODE_W:
mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen;
@@ -2395,7 +2417,7 @@
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
- if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "onKeyLongPress(" + event);
+ if (DeveloperPreferences.LOG_KEYEVENT.get(this)) Log.d(TAG, "onKeyLongPress(" + event);
if (USE_BACK_KEY_LONG_PRESS) {
// Treat the BACK key long press as the normal press since we changed the behavior in
// onBackPressed().
@@ -2465,7 +2487,7 @@
}
private boolean dispatchKeyEventToSession(final KeyEvent event) {
- if (SystemProperties.LOG_KEYEVENT.getValue()) {
+ if (DeveloperPreferences.LOG_KEYEVENT.get(this)) {
Log.d(TAG, "dispatchKeyEventToSession(" + event + ")");
}
boolean handled = false;
@@ -2778,6 +2800,11 @@
}
}
+ @Override
+ public AndroidInjector<Object> androidInjector() {
+ return mAndroidInjector;
+ }
+
private static class MainActivityHandler extends WeakHandler<MainActivity> {
MainActivityHandler(MainActivity mainActivity) {
super(mainActivity);
@@ -2823,11 +2850,8 @@
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.MyOnTuneListener.onTune");
mChannel = channel;
mWasUnderShrunkenTvView = wasUnderShrunkenTvView;
-
- if (mBackendKnobs.enablePartialProgramFetch()) {
- // Fetch complete projection of tuned channel.
- mProgramDataManager.prefetchChannel(channel.getId());
- }
+ // Fetch complete projection of tuned channel.
+ mProgramDataManager.onChannelTuned(channel.getId());
}
@Override
@@ -2982,5 +3006,14 @@
public abstract static class Module {
@ContributesAndroidInjector
abstract MainActivity contributesMainActivityActivityInjector();
+
+ @ContributesAndroidInjector
+ abstract DeveloperOptionFragment contributesDeveloperOptionFragment();
+
+ @ContributesAndroidInjector
+ abstract RatingsFragment contributesRatingsFragment();
+
+ @ContributesAndroidInjector
+ abstract ProgramItemView contributesProgramItemView();
}
}
diff --git a/src/com/android/tv/MediaSessionWrapper.java b/src/com/android/tv/MediaSessionWrapper.java
index a647a06..df88639 100644
--- a/src/com/android/tv/MediaSessionWrapper.java
+++ b/src/com/android/tv/MediaSessionWrapper.java
@@ -34,8 +34,9 @@
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.data.Program;
+
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.util.Utils;
import com.android.tv.util.images.ImageLoader;
diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java
index 5185b12..25049f1 100644
--- a/src/com/android/tv/SetupPassthroughActivity.java
+++ b/src/com/android/tv/SetupPassthroughActivity.java
@@ -18,7 +18,7 @@
import android.app.Activity;
import android.content.ActivityNotFoundException;
-import android.content.Context;
+import android.content.ComponentName;
import android.content.Intent;
import android.media.tv.TvInputInfo;
import android.os.Bundle;
@@ -26,6 +26,8 @@
import android.os.Looper;
import android.support.annotation.MainThread;
import android.util.Log;
+
+import com.android.tv.common.CommonConstants;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.actions.InputSetupActionUtils;
import com.android.tv.data.ChannelDataManager;
@@ -36,9 +38,16 @@
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
+
import com.google.android.tv.partner.support.EpgContract;
+
+import dagger.android.AndroidInjection;
+import dagger.android.ContributesAndroidInjector;
+
import java.util.concurrent.TimeUnit;
+import javax.inject.Inject;
+
/**
* An activity to launch a TV input setup activity.
*
@@ -55,18 +64,20 @@
private TvInputInfo mTvInputInfo;
private Intent mActivityAfterCompletion;
private boolean mEpgFetcherDuringScan;
- private EpgInputWhiteList mEpgInputWhiteList;
+ @Inject EpgInputWhiteList mEpgInputWhiteList;
+ @Inject TvInputManagerHelper mInputManager;
+ @Inject SetupUtils mSetupUtils;
+ @Inject ChannelDataManager mChannelDataManager;
+ @Inject EpgFetcher mEpgFetcher;
@Override
public void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate");
+ AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
- TvSingletons tvSingletons = TvSingletons.getSingletons(this);
- TvInputManagerHelper inputManager = tvSingletons.getTvInputManagerHelper();
Intent intent = getIntent();
String inputId = intent.getStringExtra(InputSetupActionUtils.EXTRA_INPUT_ID);
- mTvInputInfo = inputManager.getTvInputInfo(inputId);
- mEpgInputWhiteList = new EpgInputWhiteList(tvSingletons.getCloudEpgFlags());
+ mTvInputInfo = mInputManager.getTvInputInfo(inputId);
mActivityAfterCompletion = InputSetupActionUtils.getExtraActivityAfter(intent);
boolean needToFetchEpg =
mTvInputInfo != null && Utils.isInternalTvInput(this, mTvInputInfo.getId());
@@ -106,6 +117,17 @@
InputSetupActionUtils.removeSetupIntent(extras);
setupIntent.putExtras(extras);
try {
+ ComponentName callingActivity = getCallingActivity();
+ if (callingActivity != null
+ && !callingActivity.getPackageName().equals(CommonConstants.BASE_PACKAGE)) {
+ Log.w(
+ TAG,
+ "Calling activity "
+ + callingActivity.getPackageName()
+ + " is not trusted. Not forwarding intent.");
+ finish();
+ return;
+ }
startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Can't find activity: " + setupIntent.getComponent());
@@ -114,10 +136,10 @@
}
if (needToFetchEpg) {
if (sScanTimeoutMonitor == null) {
- sScanTimeoutMonitor = new ScanTimeoutMonitor(this);
+ sScanTimeoutMonitor = new ScanTimeoutMonitor(mEpgFetcher, mChannelDataManager);
}
sScanTimeoutMonitor.startMonitoring();
- TvSingletons.getSingletons(this).getEpgFetcher().onChannelScanStarted();
+ mEpgFetcher.onChannelScanStarted();
}
}
}
@@ -133,15 +155,25 @@
boolean setupComplete =
requestCode == REQUEST_START_SETUP_ACTIVITY && resultCode == Activity.RESULT_OK;
// Tells EpgFetcher that channel source setup is finished.
- EpgFetcher epgFetcher = TvSingletons.getSingletons(this).getEpgFetcher();
+
if (mEpgFetcherDuringScan) {
- epgFetcher.onChannelScanFinished();
+ mEpgFetcher.onChannelScanFinished();
}
if (!setupComplete) {
setResult(resultCode, data);
finish();
return;
}
+ if (TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.isEnabled(this)
+ && data != null
+ && data.getBooleanExtra(EpgContract.EXTRA_USE_CLOUD_EPG, false)) {
+ if (DEBUG) Log.d(TAG, "extra " + data.getExtras());
+ String inputId = data.getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+ if (mEpgInputWhiteList.isInputWhiteListed(inputId)) {
+ mEpgFetcher.fetchImmediately();
+ }
+ }
+
if (mTvInputInfo == null) {
Log.w(
TAG,
@@ -152,21 +184,19 @@
finish();
return;
}
- TvSingletons.getSingletons(this)
- .getSetupUtils()
- .onTvInputSetupFinished(
- mTvInputInfo.getId(),
- () -> {
- if (mActivityAfterCompletion != null) {
- try {
- startActivity(mActivityAfterCompletion);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "Activity launch failed", e);
- }
- }
- setResult(resultCode, data);
- finish();
- });
+ mSetupUtils.onTvInputSetupFinished(
+ mTvInputInfo.getId(),
+ () -> {
+ if (mActivityAfterCompletion != null) {
+ try {
+ startActivity(mActivityAfterCompletion);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity launch failed", e);
+ }
+ }
+ setResult(resultCode, data);
+ finish();
+ });
}
/**
@@ -179,7 +209,7 @@
// Set timeout long enough. The message in Sony TV says the scanning takes about 30 minutes.
private static final long SCAN_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(30);
- private final Context mContext;
+ private final EpgFetcher mEpgFetcher;
private final ChannelDataManager mChannelDataManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Runnable mScanTimeoutRunnable =
@@ -207,9 +237,9 @@
};
private boolean mStarted;
- private ScanTimeoutMonitor(Context context) {
- mContext = context.getApplicationContext();
- mChannelDataManager = TvSingletons.getSingletons(context).getChannelDataManager();
+ private ScanTimeoutMonitor(EpgFetcher epgFetcher, ChannelDataManager mChannelDataManager) {
+ mEpgFetcher = epgFetcher;
+ this.mChannelDataManager = mChannelDataManager;
}
private void startMonitoring() {
@@ -237,7 +267,14 @@
private void onScanTimedOut() {
stopMonitoring();
- TvSingletons.getSingletons(mContext).getEpgFetcher().onChannelScanFinished();
+ mEpgFetcher.onChannelScanFinished();
}
}
+
+ /** Exports {@link MainActivity} for Dagger codegen to create the appropriate injector. */
+ @dagger.Module
+ public abstract static class Module {
+ @ContributesAndroidInjector
+ abstract SetupPassthroughActivity contributesSetupPassthroughActivity();
+ }
}
diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java
index 779e8df..f08b5e8 100644
--- a/src/com/android/tv/TimeShiftManager.java
+++ b/src/com/android/tv/TimeShiftManager.java
@@ -26,18 +26,21 @@
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Range;
+
import com.android.tv.analytics.Tracker;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.WeakHandler;
import com.android.tv.data.OnCurrentProgramUpdatedListener;
-import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.ui.TunableTvView;
import com.android.tv.ui.api.TunableTvViewPlayingApi.TimeShiftListener;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.TimeShiftUtils;
import com.android.tv.util.Utils;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -50,7 +53,7 @@
import java.util.concurrent.TimeUnit;
/**
- * A class which manages the time shift feature in Live TV. It consists of two parts. {@link
+ * A class which manages the time shift feature in TV app. It consists of two parts. {@link
* PlayController} controls the playback such as play/pause, rewind and fast-forward using {@link
* TunableTvView} which communicates with TvInputService through {@link
* android.media.tv.TvInputService.Session}. {@link ProgramManager} loads programs of the current
@@ -144,8 +147,8 @@
DISABLE_ACTION_THRESHOLD + 3 * REQUEST_CURRENT_POSITION_INTERVAL;
/**
* The current position sent from TIS can not be exactly the same as the current system time due
- * to the elapsed time to pass the message from TIS to Live TV. So the boundary threshold
- * is necessary. The same goes for the recording start time. It's the same {@link
+ * to the elapsed time to pass the message from TIS to TV app. So the boundary threshold is
+ * necessary. The same goes for the recording start time. It's the same {@link
* #REQUEST_CURRENT_POSITION_INTERVAL}.
*/
private static final long RECORDING_BOUNDARY_THRESHOLD = REQUEST_CURRENT_POSITION_INTERVAL;
@@ -619,8 +622,8 @@
< mAvailablityChangedTimeMs - ALLOWED_START_TIME_OFFSET) {
Log.e(
TAG,
- "The start time is too earlier than the time of availability: {"
- + "startTime: "
+ "The start time is too earlier than the time of"
+ + " availability: {startTime: "
+ recordStartTimeMs
+ ", availability: "
+ mAvailablityChangedTimeMs);
@@ -632,9 +635,9 @@
// clock,, use system's current time instead.
Log.e(
TAG,
- "The start time should not be earlier than the current time, "
- + "reset the start time to the system's current time: {"
- + "startTime: "
+ "The start time should not be earlier than the current"
+ + " time, reset the start time to the system's current"
+ + " time: {startTime: "
+ recordStartTimeMs
+ ", current time: "
+ System.currentTimeMillis());
@@ -1103,7 +1106,7 @@
long end = Utils.ceilTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
while (end < endTimeMs) {
programs.add(
- new Program.Builder()
+ new ProgramImpl.Builder()
.setStartTimeUtcMillis(start)
.setEndTimeUtcMillis(end)
.build());
@@ -1111,7 +1114,7 @@
end += MAX_DUMMY_PROGRAM_DURATION;
}
programs.add(
- new Program.Builder()
+ new ProgramImpl.Builder()
.setStartTimeUtcMillis(start)
.setEndTimeUtcMillis(endTimeMs)
.build());
diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java
index 5f25a24..bc8226c 100644
--- a/src/com/android/tv/TvApplication.java
+++ b/src/com/android/tv/TvApplication.java
@@ -35,20 +35,19 @@
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Toast;
+
import com.android.tv.common.BaseApplication;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
-import com.android.tv.common.util.Clock;
import com.android.tv.common.util.Debug;
import com.android.tv.common.util.SharedPreferencesUtils;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.epg.EpgFetcher;
-import com.android.tv.data.epg.EpgFetcherImpl;
+import com.android.tv.data.epg.EpgReader;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.DvrDataManagerImpl;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.DvrStorageStatusManager;
@@ -56,8 +55,9 @@
import com.android.tv.dvr.recorder.RecordingScheduler;
import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
import com.android.tv.features.TvFeatures;
-import com.android.tv.perf.PerformanceMonitorManager;
-import com.android.tv.perf.PerformanceMonitorManagerFactory;
+import com.android.tv.perf.PerformanceMonitor;
+import com.android.tv.perf.StartupMeasure;
+import com.android.tv.perf.StartupMeasureFactory;
import com.android.tv.recommendation.ChannelPreviewUpdater;
import com.android.tv.recommendation.RecordedProgramPreviewUpdater;
import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
@@ -66,27 +66,30 @@
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
+
import com.google.common.base.Optional;
+
import dagger.Lazy;
+
+import com.android.tv.common.flags.CloudEpgFlags;
+import com.android.tv.common.flags.LegacyFlags;
+
import java.util.List;
import java.util.concurrent.Executor;
+
import javax.inject.Inject;
/**
- * Live TV application.
+ * TV application.
*
* <p>This includes all the Google specific hooks.
*/
public abstract class TvApplication extends BaseApplication implements TvSingletons, Starter {
- protected static final PerformanceMonitorManager PERFORMANCE_MONITOR_MANAGER =
- PerformanceMonitorManagerFactory.create();
+ protected static final StartupMeasure STARTUP_MEASURE = StartupMeasureFactory.create();
private static final String TAG = "TvApplication";
private static final boolean DEBUG = false;
- /** Namespace for LiveChannels configs. LiveChannels configs are kept in piper. */
- public static final String CONFIGNS_P4 = "configns:p4";
-
/**
* Broadcast Action: The user has updated LC to a new version that supports tuner input. {@link
* TunerInputController} will receive this intent to check the existence of tuner input when the
@@ -102,12 +105,12 @@
private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper();
private SelectInputActivity mSelectInputActivity;
- private ChannelDataManager mChannelDataManager;
+ @Inject Lazy<ChannelDataManager> mChannelDataManager;
private volatile ProgramDataManager mProgramDataManager;
private PreviewDataManager mPreviewDataManager;
private DvrManager mDvrManager;
private DvrScheduleManager mDvrScheduleManager;
- private DvrDataManager mDvrDataManager;
+ @Inject Lazy<DvrDataManager> mDvrDataManager;
private DvrWatchedPositionManager mDvrWatchedPositionManager;
private RecordingScheduler mRecordingScheduler;
private RecordingStorageStatusManager mDvrStorageStatusManager;
@@ -117,11 +120,16 @@
private Boolean mRunningInMainProcess;
@Inject Lazy<TvInputManagerHelper> mLazyTvInputManagerHelper;
private boolean mStarted;
- private EpgFetcher mEpgFetcher;
+ @Inject EpgFetcher mEpgFetcher;
@Inject Optional<BuiltInTunerManager> mOptionalBuiltInTunerManager;
@Inject SetupUtils mSetupUtils;
@Inject @DbExecutor Executor mDbExecutor;
+ @Inject Lazy<EpgReader> mEpgReader;
+ @Inject BuildType mBuildType;
+ @Inject CloudEpgFlags mCloudEpgFlags;
+ @Inject LegacyFlags mLegacyFlags;
+ @Inject PerformanceMonitor mPerformanceMonitor;
@Override
public void onCreate() {
@@ -132,6 +140,8 @@
throw new IllegalStateException(msg);
}
super.onCreate();
+ mPerformanceMonitor.startMemoryMonitor();
+ mPerformanceMonitor.startCrashMonitor();
SharedPreferencesUtils.initialize(
this,
() -> {
@@ -146,16 +156,15 @@
Log.w(TAG, "Unable to find package '" + getPackageName() + "'.", e);
mVersionName = "";
}
- Log.i(TAG, "Starting Live TV " + getVersionName());
+ Log.i(TAG, "Starting TV app " + getVersionName());
// In SetupFragment, transitions are set in the constructor. Because the fragment can be
// created in Activity.onCreate() by the framework, SetupAnimationHelper should be
// initialized here before Activity.onCreate() is called.
- mEpgFetcher = EpgFetcherImpl.create(this);
SetupAnimationHelper.initialize(this);
getTvInputManagerHelper();
- Log.i(TAG, "Started Live TV " + mVersionName);
+ Log.i(TAG, "Started TV app " + mVersionName);
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate");
}
@@ -210,8 +219,10 @@
mEpgFetcher.startRoutineService();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ChannelPreviewUpdater.getInstance(this).startRoutineService();
- RecordedProgramPreviewUpdater.getInstance(this)
- .updatePreviewDataForRecordedPrograms();
+ if (CommonFeatures.DVR.isEnabled(this)) {
+ RecordedProgramPreviewUpdater.getInstance(this)
+ .updatePreviewDataForRecordedPrograms();
+ }
}
}
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.start");
@@ -237,11 +248,6 @@
}
@Override
- public EpgFetcher getEpgFetcher() {
- return mEpgFetcher;
- }
-
- @Override
public synchronized SetupUtils getSetupUtils() {
return mSetupUtils;
}
@@ -286,16 +292,7 @@
/** Returns {@link ChannelDataManager}. */
@Override
public ChannelDataManager getChannelDataManager() {
- if (mChannelDataManager == null) {
- mChannelDataManager = new ChannelDataManager(this, getTvInputManagerHelper());
- mChannelDataManager.start();
- }
- return mChannelDataManager;
- }
-
- @Override
- public boolean isChannelDataManagerLoadFinished() {
- return mChannelDataManager != null && mChannelDataManager.isDbLoadFinished();
+ return mChannelDataManager.get();
}
/** Returns {@link ProgramDataManager}. */
@@ -314,11 +311,6 @@
return mProgramDataManager;
}
- @Override
- public boolean isProgramDataManagerCurrentProgramsLoadFinished() {
- return mProgramDataManager != null && mProgramDataManager.isCurrentProgramsLoadFinished();
- }
-
/** Returns {@link PreviewDataManager}. */
@TargetApi(Build.VERSION_CODES.O)
@Override
@@ -334,12 +326,7 @@
@TargetApi(Build.VERSION_CODES.N)
@Override
public DvrDataManager getDvrDataManager() {
- if (mDvrDataManager == null) {
- DvrDataManagerImpl dvrDataManager = new DvrDataManagerImpl(this, Clock.SYSTEM);
- mDvrDataManager = dvrDataManager;
- dvrDataManager.start();
- }
- return mDvrDataManager;
+ return mDvrDataManager.get();
}
@Override
@@ -351,6 +338,11 @@
return mDvrStorageStatusManager;
}
+ @Override
+ public PerformanceMonitor getPerformanceMonitor() {
+ return mPerformanceMonitor;
+ }
+
/** Returns the main activity information. */
@Override
public MainActivityWrapper getMainActivityWrapper() {
@@ -450,7 +442,7 @@
}
/**
- * Returns the version name of the live channels.
+ * Returns the version name of the TV app.
*
* @see PackageInfo#versionName
*/
@@ -489,6 +481,37 @@
Optional<String> optionalEmbeddedTunerInputId =
mOptionalBuiltInTunerManager.transform(
BuiltInTunerManager::getEmbeddedTunerInputId);
+ // If there is only play movies trailer input, we don't handle input count change.
+ final String playMoviesInputIdPrefix = "com.google.android.videos/";
+ int tunerInputCount = 0;
+ boolean hasPlayMoviesInput = false;
+ for (TvInputInfo input : inputs) {
+ if (calledByTunerServiceChanged
+ && !tunerServiceEnabled
+ && optionalEmbeddedTunerInputId.isPresent()
+ && optionalEmbeddedTunerInputId.get().equals(input.getId())) {
+ continue;
+ }
+ if (input.getType() == TvInputInfo.TYPE_TUNER) {
+ if (DEBUG) Log.d(TAG, "Tuner input: " + input.getId());
+ ++tunerInputCount;
+ if (input.getId().startsWith(playMoviesInputIdPrefix)) {
+ hasPlayMoviesInput = true;
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Input count: "
+ + tunerInputCount
+ + " hasPlayMoviesChannel: "
+ + hasPlayMoviesInput);
+ }
+ if (tunerInputCount == 1 && hasPlayMoviesInput) {
+ if (DEBUG) Log.d(TAG, "There is only play movies input");
+ skipTunerInputCheck = true;
+ }
// Enable the TvActivity only if there is at least one tuner type input.
if (!skipTunerInputCheck) {
for (TvInputInfo input : inputs) {
@@ -515,13 +538,24 @@
if (packageManager.getComponentEnabledSetting(name) != newState) {
packageManager.setComponentEnabledSetting(
name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0);
- Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV.");
+ Log.i(TAG, (enable ? "Un-hide" : "Hide") + " TV app.");
}
mSetupUtils.onInputListUpdated(inputManager);
}
@Override
+ @DbExecutor
public Executor getDbExecutor() {
return mDbExecutor;
}
+
+ @Override
+ public Lazy<EpgReader> providesEpgReader() {
+ return mEpgReader;
+ }
+
+ @Override
+ public BuildType getBuildType() {
+ return mBuildType;
+ }
}
diff --git a/src/com/android/tv/TvSingletons.java b/src/com/android/tv/TvSingletons.java
index 20edf3d..9e4f462 100644
--- a/src/com/android/tv/TvSingletons.java
+++ b/src/com/android/tv/TvSingletons.java
@@ -17,16 +17,15 @@
package com.android.tv;
import android.content.Context;
+
import com.android.tv.analytics.Analytics;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.BaseApplication;
import com.android.tv.common.BaseSingletons;
-import com.android.tv.common.experiments.ExperimentLoader;
import com.android.tv.common.flags.has.HasUiFlags;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.ProgramDataManager;
-import com.android.tv.data.epg.EpgFetcher;
import com.android.tv.data.epg.EpgReader;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
@@ -37,14 +36,27 @@
import com.android.tv.tunerinputcontroller.HasBuiltInTunerManager;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
-import com.android.tv.util.account.AccountHelper;
+
+import dagger.Lazy;
+
import com.android.tv.common.flags.BackendKnobsFlags;
+
import java.util.concurrent.Executor;
-import javax.inject.Provider;
/** Interface with getters for application scoped singletons. */
public interface TvSingletons extends BaseSingletons, HasBuiltInTunerManager, HasUiFlags {
+ /*
+ * Do not add any new methods here.
+ *
+ * To move a getter to Injection.
+ * 1. Make a type injectable @Singleton.
+ * 2. Mark the getter here as deprecated.
+ * 3. Lazily inject the object in TvApplication.
+ * 4. Move easy usages of getters to injection instead.
+ * 5. Delete the method when all usages are migrated.
+ */
+
/**
* Returns the @{@link TvSingletons} using the application context.
*
@@ -62,24 +74,14 @@
@Deprecated
ChannelDataManager getChannelDataManager();
- /**
- * Checks if the {@link ChannelDataManager} instance has been created and all the channels has
- * been loaded.
- */
- boolean isChannelDataManagerLoadFinished();
-
/** @deprecated use injection instead. */
@Deprecated
ProgramDataManager getProgramDataManager();
- /**
- * Checks if the {@link ProgramDataManager} instance has been created and the current programs
- * for all the channels has been loaded.
- */
- boolean isProgramDataManagerCurrentProgramsLoadFinished();
-
PreviewDataManager getPreviewDataManager();
+ /** @deprecated use injection instead. */
+ @Deprecated
DvrDataManager getDvrDataManager();
DvrScheduleManager getDvrScheduleManager();
@@ -88,6 +90,8 @@
RecordingScheduler getRecordingScheduler();
+ /** @deprecated use injection instead. */
+ @Deprecated
DvrWatchedPositionManager getDvrWatchedPositionManager();
InputSessionManager getInputSessionManager();
@@ -96,26 +100,22 @@
MainActivityWrapper getMainActivityWrapper();
- AccountHelper getAccountHelper();
-
boolean isRunningInMainProcess();
+ /** @deprecated use injection instead. */
+ @Deprecated
PerformanceMonitor getPerformanceMonitor();
/** @deprecated use injection instead. */
@Deprecated
TvInputManagerHelper getTvInputManagerHelper();
- Provider<EpgReader> providesEpgReader();
-
- EpgFetcher getEpgFetcher();
+ Lazy<EpgReader> providesEpgReader();
/** @deprecated use injection instead. */
@Deprecated
SetupUtils getSetupUtils();
- ExperimentLoader getExperimentLoader();
-
/** @deprecated use injection instead. */
@Deprecated
Executor getDbExecutor();
diff --git a/src/com/android/tv/app/LiveTvApplication.java b/src/com/android/tv/app/LiveTvApplication.java
index 38e85e4..8090653 100644
--- a/src/com/android/tv/app/LiveTvApplication.java
+++ b/src/com/android/tv/app/LiveTvApplication.java
@@ -22,50 +22,34 @@
import com.android.tv.analytics.StubAnalytics;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.dagger.ApplicationModule;
-import com.android.tv.common.experiments.ExperimentLoader;
import com.android.tv.common.flags.impl.DefaultBackendKnobsFlags;
import com.android.tv.common.flags.impl.DefaultCloudEpgFlags;
-import com.android.tv.common.flags.impl.DefaultConcurrentDvrPlaybackFlags;
import com.android.tv.common.flags.impl.DefaultUiFlags;
import com.android.tv.common.singletons.HasSingletons;
-import com.android.tv.data.epg.EpgReader;
-import com.android.tv.data.epg.StubEpgReader;
import com.android.tv.modules.TvSingletonsModule;
import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.perf.PerformanceMonitorManagerFactory;
import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
-import com.android.tv.util.account.AccountHelper;
-import com.android.tv.util.account.AccountHelperImpl;
+
import com.google.common.base.Optional;
+
import dagger.android.AndroidInjector;
-import javax.inject.Provider;
+
+import javax.inject.Inject;
/** The top level application for Live TV. */
public class LiveTvApplication extends TvApplication implements HasSingletons<TvSingletons> {
static {
- PERFORMANCE_MONITOR_MANAGER.getStartupMeasure().onAppClassLoaded();
+ STARTUP_MEASURE.onAppClassLoaded();
}
- private final Provider<EpgReader> mEpgReaderProvider =
- new Provider<EpgReader>() {
-
- @Override
- public EpgReader get() {
- return new StubEpgReader(LiveTvApplication.this);
- }
- };
-
private final DefaultBackendKnobsFlags mBackendKnobsFlags = new DefaultBackendKnobsFlags();
private final DefaultCloudEpgFlags mCloudEpgFlags = new DefaultCloudEpgFlags();
private final DefaultUiFlags mUiFlags = new DefaultUiFlags();
- private final DefaultConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags =
- new DefaultConcurrentDvrPlaybackFlags();
- private AccountHelper mAccountHelper;
+
private Analytics mAnalytics;
private Tracker mTracker;
- private ExperimentLoader mExperimentLoader;
- private PerformanceMonitor mPerformanceMonitor;
+ @Inject PerformanceMonitor mPerformanceMonitor;
@Override
protected AndroidInjector<LiveTvApplication> applicationInjector() {
@@ -78,38 +62,15 @@
@Override
public void onCreate() {
super.onCreate();
- PERFORMANCE_MONITOR_MANAGER.getStartupMeasure().onAppCreate(this);
- }
-
- /** Returns the {@link AccountHelperImpl}. */
- @Override
- public AccountHelper getAccountHelper() {
- if (mAccountHelper == null) {
- mAccountHelper = new AccountHelperImpl(getApplicationContext());
- }
- return mAccountHelper;
+ STARTUP_MEASURE.onAppCreate(this);
}
@Override
- public synchronized PerformanceMonitor getPerformanceMonitor() {
- if (mPerformanceMonitor == null) {
- mPerformanceMonitor = PerformanceMonitorManagerFactory.create().initialize(this);
- }
+ public PerformanceMonitor getPerformanceMonitor() {
return mPerformanceMonitor;
}
@Override
- public Provider<EpgReader> providesEpgReader() {
- return mEpgReaderProvider;
- }
-
- @Override
- public ExperimentLoader getExperimentLoader() {
- mExperimentLoader = new ExperimentLoader();
- return mExperimentLoader;
- }
-
- @Override
public DefaultBackendKnobsFlags getBackendKnobs() {
return mBackendKnobsFlags;
}
@@ -148,16 +109,6 @@
}
@Override
- public BuildType getBuildType() {
- return BuildType.AOSP;
- }
-
- @Override
- public DefaultConcurrentDvrPlaybackFlags getConcurrentDvrPlaybackFlags() {
- return mConcurrentDvrPlaybackFlags;
- }
-
- @Override
public TvSingletons singletons() {
return this;
}
diff --git a/src/com/android/tv/app/LiveTvApplicationComponent.java b/src/com/android/tv/app/LiveTvApplicationComponent.java
index 3d3f049..71ce1a8 100644
--- a/src/com/android/tv/app/LiveTvApplicationComponent.java
+++ b/src/com/android/tv/app/LiveTvApplicationComponent.java
@@ -15,6 +15,7 @@
*/
package com.android.tv.app;
+import com.android.tv.search.LocalSearchProvider;
import dagger.Component;
import dagger.android.AndroidInjectionModule;
import dagger.android.AndroidInjector;
@@ -22,5 +23,10 @@
/** Dagger component for {@link LiveTvApplication}. */
@Singleton
-@Component(modules = {AndroidInjectionModule.class, LiveTvModule.class})
+@Component(
+ modules = {
+ AndroidInjectionModule.class,
+ LiveTvModule.class,
+ LocalSearchProvider.Module.class
+ })
public interface LiveTvApplicationComponent extends AndroidInjector<LiveTvApplication> {}
diff --git a/src/com/android/tv/app/LiveTvModule.java b/src/com/android/tv/app/LiveTvModule.java
index a28749b..db631bc 100644
--- a/src/com/android/tv/app/LiveTvModule.java
+++ b/src/com/android/tv/app/LiveTvModule.java
@@ -16,18 +16,49 @@
package com.android.tv.app;
import com.android.tv.common.flags.impl.DefaultFlagsModule;
+import com.android.tv.data.epg.EpgReader;
+import com.android.tv.data.epg.StubEpgReader;
import com.android.tv.modules.TvApplicationModule;
+import com.android.tv.perf.PerformanceMonitor;
+import com.android.tv.perf.stub.StubPerformanceMonitor;
import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
+import com.android.tv.ui.sidepanel.DeveloperOptionFragment;
+import com.android.tv.util.account.AccountHelper;
+import com.android.tv.util.account.AccountHelperImpl;
import com.google.common.base.Optional;
import dagger.Module;
import dagger.Provides;
+import javax.inject.Singleton;
/** Dagger module for {@link LiveTvApplication}. */
@Module(includes = {DefaultFlagsModule.class, TvApplicationModule.class})
class LiveTvModule {
@Provides
+ static AccountHelper providesAccountHelper(AccountHelperImpl impl) {
+ return impl;
+ }
+
+ @Provides
+ static Optional<DeveloperOptionFragment.AdditionalDeveloperItemsFactory>
+ providesAdditionalDeveloperItemsFactory() {
+ return Optional.absent();
+ }
+
+ @Provides
Optional<BuiltInTunerManager> providesBuiltInTunerManager() {
return Optional.absent();
}
+
+ @Provides
+ @Singleton
+ PerformanceMonitor providesPerformanceMonitor() {
+ return new StubPerformanceMonitor();
+ }
+
+ @Provides
+ @Singleton
+ static EpgReader providesEpgReader(StubEpgReader impl) {
+ return impl;
+ }
}
diff --git a/src/com/android/tv/data/BaseProgram.java b/src/com/android/tv/data/BaseProgram.java
deleted file mode 100644
index 9650fd1..0000000
--- a/src/com/android/tv/data/BaseProgram.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2016 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.data;
-
-import android.content.Context;
-import android.media.tv.TvContentRating;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import com.android.tv.R;
-import com.google.common.collect.ImmutableList;
-import java.util.Comparator;
-import java.util.Objects;
-
-/**
- * Base class for {@link com.android.tv.data.Program} and {@link
- * com.android.tv.dvr.data.RecordedProgram}.
- */
-public abstract class BaseProgram {
- /**
- * Comparator used to compare {@link BaseProgram} according to its season and episodes number.
- * If a program's season or episode number is null, it will be consider "smaller" than programs
- * with season or episode numbers.
- */
- public static final Comparator<BaseProgram> EPISODE_COMPARATOR = new EpisodeComparator(false);
-
- /**
- * Comparator used to compare {@link BaseProgram} according to its season and episodes number
- * with season numbers in a reversed order. If a program's season or episode number is null, it
- * will be consider "smaller" than programs with season or episode numbers.
- */
- public static final Comparator<BaseProgram> SEASON_REVERSED_EPISODE_COMPARATOR =
- new EpisodeComparator(true);
-
- public static final String COLUMN_SERIES_ID = "series_id";
-
- public static final String COLUMN_STATE = "state";
-
- private static class EpisodeComparator implements Comparator<BaseProgram> {
- private final boolean mReversedSeason;
-
- EpisodeComparator(boolean reversedSeason) {
- mReversedSeason = reversedSeason;
- }
-
- @Override
- public int compare(BaseProgram lhs, BaseProgram rhs) {
- if (lhs == rhs) {
- return 0;
- }
- int seasonNumberCompare = numberCompare(lhs.getSeasonNumber(), rhs.getSeasonNumber());
- if (seasonNumberCompare != 0) {
- return mReversedSeason ? -seasonNumberCompare : seasonNumberCompare;
- } else {
- return numberCompare(lhs.getEpisodeNumber(), rhs.getEpisodeNumber());
- }
- }
- }
-
- /** Compares two strings represent season numbers or episode numbers of programs. */
- public static int numberCompare(String s1, String s2) {
- if (Objects.equals(s1, s2)) {
- return 0;
- } else if (s1 == null) {
- return -1;
- } else if (s2 == null) {
- return 1;
- } else if (s1.equals(s2)) {
- return 0;
- }
- try {
- return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2));
- } catch (NumberFormatException e) {
- return s1.compareTo(s2);
- }
- }
-
- /** Returns ID of the program. */
- public abstract long getId();
-
- /** Returns the title of the program. */
- public abstract String getTitle();
-
- /** Returns the episode title. */
- public abstract String getEpisodeTitle();
-
- /** Returns the displayed title of the program episode. */
- @Nullable
- public String getEpisodeDisplayTitle(Context context) {
- String episodeNumber = getEpisodeNumber();
- String episodeTitle = getEpisodeTitle();
- if (!TextUtils.isEmpty(episodeNumber)) {
- episodeTitle = episodeTitle == null ? "" : episodeTitle;
- String seasonNumber = getSeasonNumber();
- if (TextUtils.isEmpty(seasonNumber) || TextUtils.equals(seasonNumber, "0")) {
- // Do not show "S0: ".
- return context.getResources()
- .getString(
- R.string.display_episode_title_format_no_season_number,
- episodeNumber,
- episodeTitle);
- } else {
- return context.getResources()
- .getString(
- R.string.display_episode_title_format,
- seasonNumber,
- episodeNumber,
- episodeTitle);
- }
- }
- return episodeTitle;
- }
-
- /**
- * Returns the content description of the program episode, suitable for being spoken by an
- * accessibility service.
- */
- public String getEpisodeContentDescription(Context context) {
- String episodeNumber = getEpisodeNumber();
- String episodeTitle = getEpisodeTitle();
- if (!TextUtils.isEmpty(episodeNumber)) {
- episodeTitle = episodeTitle == null ? "" : episodeTitle;
- String seasonNumber = getSeasonNumber();
- if (TextUtils.isEmpty(seasonNumber) || TextUtils.equals(seasonNumber, "0")) {
- // Do not list season if it is empty or 0
- return context.getResources()
- .getString(
- R.string.content_description_episode_format_no_season_number,
- episodeNumber,
- episodeTitle);
- } else {
- return context.getResources()
- .getString(
- R.string.content_description_episode_format,
- seasonNumber,
- episodeNumber,
- episodeTitle);
- }
- }
- return episodeTitle;
- }
-
- /** Returns the description of the program. */
- public abstract String getDescription();
-
- /** Returns the long description of the program. */
- public abstract String getLongDescription();
-
- /** Returns the start time of the program in Milliseconds. */
- public abstract long getStartTimeUtcMillis();
-
- /** Returns the end time of the program in Milliseconds. */
- public abstract long getEndTimeUtcMillis();
-
- /** Returns the duration of the program in Milliseconds. */
- public abstract long getDurationMillis();
-
- /** Returns the series ID. */
- @Nullable
- public abstract String getSeriesId();
-
- /** Returns the season number. */
- public abstract String getSeasonNumber();
-
- /** Returns the episode number. */
- public abstract String getEpisodeNumber();
-
- /** Returns URI of the program's poster. */
- public abstract String getPosterArtUri();
-
- /** Returns URI of the program's thumbnail. */
- public abstract String getThumbnailUri();
-
- /** Returns the array of the ID's of the canonical genres. */
- public abstract int[] getCanonicalGenreIds();
-
- /** Returns the array of content ratings. */
- public abstract ImmutableList<TvContentRating> getContentRatings();
-
- /** Returns channel's ID of the program. */
- public abstract long getChannelId();
-
- /** Returns if the program is valid. */
- public abstract boolean isValid();
-
- /** Checks whether the program is episodic or not. */
- public boolean isEpisodic() {
- return getSeriesId() != null;
- }
-
- /** Generates the series ID for the other inputs than the tuner TV input. */
- public static String generateSeriesId(String packageName, String title) {
- return packageName + "/" + title;
- }
-}
diff --git a/src/com/android/tv/data/BaseProgramImpl.java b/src/com/android/tv/data/BaseProgramImpl.java
new file mode 100644
index 0000000..9c8d025
--- /dev/null
+++ b/src/com/android/tv/data/BaseProgramImpl.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 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.data;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.tv.R;
+import com.android.tv.data.api.BaseProgram;
+
+/** Base class for {@link ProgramImpl} and {@link com.android.tv.dvr.data.RecordedProgram}. */
+public abstract class BaseProgramImpl implements BaseProgram {
+
+ @Override
+ @Nullable
+ public String getEpisodeDisplayTitle(Context context) {
+ String episodeNumber = getEpisodeNumber();
+ String episodeTitle = getEpisodeTitle();
+ if (!TextUtils.isEmpty(episodeNumber)) {
+ episodeTitle = episodeTitle == null ? "" : episodeTitle;
+ String seasonNumber = getSeasonNumber();
+ if (TextUtils.isEmpty(seasonNumber) || TextUtils.equals(seasonNumber, "0")) {
+ // Do not show "S0: ".
+ return context.getResources()
+ .getString(
+ R.string.display_episode_title_format_no_season_number,
+ episodeNumber,
+ episodeTitle);
+ } else {
+ return context.getResources()
+ .getString(
+ R.string.display_episode_title_format,
+ seasonNumber,
+ episodeNumber,
+ episodeTitle);
+ }
+ }
+ return episodeTitle;
+ }
+
+ @Override
+ public String getEpisodeContentDescription(Context context) {
+ String episodeNumber = getEpisodeNumber();
+ String episodeTitle = getEpisodeTitle();
+ if (!TextUtils.isEmpty(episodeNumber)) {
+ episodeTitle = episodeTitle == null ? "" : episodeTitle;
+ String seasonNumber = getSeasonNumber();
+ if (TextUtils.isEmpty(seasonNumber) || TextUtils.equals(seasonNumber, "0")) {
+ // Do not list season if it is empty or 0
+ return context.getResources()
+ .getString(
+ R.string.content_description_episode_format_no_season_number,
+ episodeNumber,
+ episodeTitle);
+ } else {
+ return context.getResources()
+ .getString(
+ R.string.content_description_episode_format,
+ seasonNumber,
+ episodeNumber,
+ episodeTitle);
+ }
+ }
+ return episodeTitle;
+ }
+
+ @Override
+ public boolean isEpisodic() {
+ return !TextUtils.isEmpty(getSeriesId());
+ }
+}
diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java
index a5c786c..67c3230 100644
--- a/src/com/android/tv/data/ChannelDataManager.java
+++ b/src/com/android/tv/data/ChannelDataManager.java
@@ -37,15 +37,18 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.MutableInt;
-import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.WeakHandler;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.common.util.PermissionUtils;
import com.android.tv.common.util.SharedPreferencesUtils;
import com.android.tv.data.api.Channel;
import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.AsyncDbTask.DbExecutor;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
@@ -56,6 +59,7 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
+import javax.inject.Singleton;
/**
* The class to manage channel data. Basic features: reading channel list and each channel's current
@@ -64,6 +68,8 @@
* methods are called in only the main thread.
*/
@AnyThread
+@AutoFactory
+@Singleton
public class ChannelDataManager {
private static final String TAG = "ChannelDataManager";
private static final boolean DEBUG = false;
@@ -143,21 +149,11 @@
};
@MainThread
- public ChannelDataManager(Context context, TvInputManagerHelper inputManager) {
- this(
- context,
- inputManager,
- TvSingletons.getSingletons(context).getDbExecutor(),
- context.getContentResolver());
- }
-
- @MainThread
- @VisibleForTesting
- ChannelDataManager(
- Context context,
- TvInputManagerHelper inputManager,
- Executor executor,
- ContentResolver contentResolver) {
+ public ChannelDataManager(
+ @Provided @ApplicationContext Context context,
+ @Provided TvInputManagerHelper inputManager,
+ @Provided @DbExecutor Executor executor,
+ @Provided ContentResolver contentResolver) {
mContext = context;
mInputManager = inputManager;
mDbExecutor = executor;
@@ -729,7 +725,7 @@
/**
* Updates a column {@code columnName} of DB table {@code uri} with the value {@code
* columnValue}. The selective rows in the ID list {@code ids} will be updated. The DB
- * operations will run on {@link TvSingletons#getDbExecutor()}.
+ * operations will run on @{@link DbExecutor}.
*/
private void updateOneColumnValue(
final String columnName, final int columnValue, final List<Long> ids) {
diff --git a/src/com/android/tv/data/InternalDataUtils.java b/src/com/android/tv/data/InternalDataUtils.java
index 4c30d39..b17ed09 100644
--- a/src/com/android/tv/data/InternalDataUtils.java
+++ b/src/com/android/tv/data/InternalDataUtils.java
@@ -19,8 +19,11 @@
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.data.Program.CriticScore;
+
+import com.android.tv.data.api.Program;
+import com.android.tv.data.api.Program.CriticScore;
import com.android.tv.dvr.data.RecordedProgram;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -49,7 +52,7 @@
* @param bytes the bytes to be deserialized
* @param builder the builder for the Program class
*/
- public static void deserializeInternalProviderData(byte[] bytes, Program.Builder builder) {
+ public static void deserializeInternalProviderData(byte[] bytes, ProgramImpl.Builder builder) {
if (bytes == null || bytes.length == 0) {
return;
}
diff --git a/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java b/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java
index edb3355..2332cda 100644
--- a/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java
+++ b/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java
@@ -16,6 +16,8 @@
package com.android.tv.data;
+import com.android.tv.data.api.Program;
+
public interface OnCurrentProgramUpdatedListener {
/** Called when the current program is updated. */
void onCurrentProgramUpdated(long channelId, Program program);
diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java
index 8616aee..dbe6028 100644
--- a/src/com/android/tv/data/PreviewDataManager.java
+++ b/src/com/android/tv/data/PreviewDataManager.java
@@ -32,10 +32,14 @@
import android.support.annotation.MainThread;
import android.util.Log;
import android.util.Pair;
+
import androidx.tvprovider.media.tv.ChannelLogoUtils;
import androidx.tvprovider.media.tv.PreviewProgram;
+
import com.android.tv.R;
import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.util.images.ImageLoader;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
@@ -218,7 +222,7 @@
Uri previewChannelsUri =
PreviewDataUtils.addQueryParamToUri(
TvContract.Channels.CONTENT_URI,
- new Pair<>(PARAM_PREVIEW, String.valueOf(true)));
+ Pair.create(PARAM_PREVIEW, String.valueOf(true)));
String packageName = mContext.getPackageName();
if (PermissionUtils.hasAccessAllEpg(mContext)) {
try (Cursor cursor =
@@ -428,10 +432,14 @@
continue;
}
try {
+ int aspectRatio =
+ ImageLoader.getAspectRatioFromPosterArtUri(
+ mContext, program.getPosterArtUri().toString());
Uri programUri =
mContentResolver.insert(
TvContract.PreviewPrograms.CONTENT_URI,
- PreviewDataUtils.createPreviewProgramFromContent(program)
+ PreviewDataUtils.createPreviewProgramFromContent(
+ program, aspectRatio)
.toContentValues());
if (programUri != null) {
long previewProgramId = ContentUris.parseId(programUri);
@@ -592,13 +600,14 @@
/** Creates a preview program. */
public static PreviewProgram createPreviewProgramFromContent(
- PreviewProgramContent program) {
+ PreviewProgramContent program, int aspectRatio) {
PreviewProgram.Builder builder = new PreviewProgram.Builder();
builder.setChannelId(program.getPreviewChannelId())
.setType(program.getType())
.setLive(program.getLive())
.setTitle(program.getTitle())
.setDescription(program.getDescription())
+ .setPosterArtAspectRatio(aspectRatio)
.setPosterArtUri(program.getPosterArtUri())
.setIntentUri(program.getIntentUri())
.setPreviewVideoUri(program.getPreviewVideoUri())
diff --git a/src/com/android/tv/data/PreviewProgramContent.java b/src/com/android/tv/data/PreviewProgramContent.java
index 8d4b88c..4ee5710 100644
--- a/src/com/android/tv/data/PreviewProgramContent.java
+++ b/src/com/android/tv/data/PreviewProgramContent.java
@@ -21,10 +21,14 @@
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Pair;
+
import androidx.tvprovider.media.tv.TvContractCompat;
+
import com.android.tv.TvSingletons;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.RecordedProgram;
+
import java.util.Objects;
/** A class to store the content of preview programs. */
@@ -41,7 +45,7 @@
private Uri mIntentUri;
private Uri mPreviewVideoUri;
- /** Create preview program content from {@link Program} */
+ /** Create preview program content from {@link ProgramImpl} */
public static PreviewProgramContent createFromProgram(
Context context, long previewChannelId, Program program) {
Channel channel =
@@ -79,7 +83,7 @@
.setIntentUri(channel.getUri())
.setPreviewVideoUri(
PreviewDataManager.PreviewDataUtils.addQueryParamToUri(
- channel.getUri(), new Pair<>(PARAM_INPUT, channel.getInputId())))
+ channel.getUri(), Pair.create(PARAM_INPUT, channel.getInputId())))
.build();
}
@@ -99,7 +103,7 @@
.setPreviewVideoUri(
PreviewDataManager.PreviewDataUtils.addQueryParamToUri(
recordedProgramUri,
- new Pair<>(PARAM_INPUT, recordedProgram.getInputId())))
+ Pair.create(PARAM_INPUT, recordedProgram.getInputId())))
.build();
}
diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java
index 2f20c89..a866c78 100644
--- a/src/com/android/tv/data/ProgramDataManager.java
+++ b/src/com/android/tv/data/ProgramDataManager.java
@@ -33,19 +33,24 @@
import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.memory.MemoryManageable;
import com.android.tv.common.util.Clock;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.perf.EventNames;
import com.android.tv.perf.PerformanceMonitor;
import com.android.tv.perf.TimerEvent;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.MultiLongSparseArray;
+import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.TvProviderUtils;
import com.android.tv.util.Utils;
+
import com.android.tv.common.flags.BackendKnobsFlags;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -75,6 +80,11 @@
private static final long CURRENT_PROGRAM_UPDATE_WAIT_MS = TimeUnit.SECONDS.toMillis(5);
@VisibleForTesting static final long PROGRAM_GUIDE_SNAP_TIME_MS = TimeUnit.MINUTES.toMillis(30);
+ // Default fetch hours
+ private static final long FETCH_HOURS_MS = TimeUnit.HOURS.toMillis(24);
+ // Load data earlier for smooth scrolling.
+ private static final long BUFFER_HOURS_MS = TimeUnit.HOURS.toMillis(6);
+
// TODO: Use TvContract constants, once they become public.
private static final String PARAM_START_TIME = "start_time";
private static final String PARAM_END_TIME = "end_time";
@@ -86,10 +96,20 @@
+ Programs.COLUMN_CHANNEL_ID
+ ", "
+ Programs.COLUMN_END_TIME_UTC_MILLIS;
+ private static final String SORT_BY_CHANNEL_ID =
+ Programs.COLUMN_CHANNEL_ID
+ + ", "
+ + Programs.COLUMN_START_TIME_UTC_MILLIS
+ + " DESC, "
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + " ASC, "
+ + Programs._ID
+ + " DESC";
private static final int MSG_UPDATE_CURRENT_PROGRAMS = 1000;
private static final int MSG_UPDATE_ONE_CURRENT_PROGRAM = 1001;
private static final int MSG_UPDATE_PREFETCH_PROGRAM = 1002;
+ private static final int MSG_UPDATE_CONTENT_RATINGS = 1003;
private final Context mContext;
private final Clock mClock;
@@ -98,6 +118,7 @@
private final BackendKnobsFlags mBackendKnobsFlags;
private final PerformanceMonitor mPerformanceMonitor;
private final ChannelDataManager mChannelDataManager;
+ private final TvInputManagerHelper mTvInputManagerHelper;
private boolean mStarted;
// Updated only on the main thread.
private volatile boolean mCurrentProgramsLoadFinished;
@@ -125,6 +146,11 @@
private boolean mPauseProgramUpdate = false;
private final LruCache<Long, Program> mZeroLengthProgramCache = new LruCache<>(10);
+ // Current tuned channel.
+ private long mTunedChannelId;
+ // Hours of data to be fetched, it is updated during horizontal scroll.
+ // Note that it should never exceed programGuideMaxHours.
+ private long mMaxFetchHoursMs = FETCH_HOURS_MS;
@MainThread
public ProgramDataManager(Context context) {
@@ -136,7 +162,8 @@
Looper.myLooper(),
TvSingletons.getSingletons(context).getBackendKnobs(),
TvSingletons.getSingletons(context).getPerformanceMonitor(),
- TvSingletons.getSingletons(context).getChannelDataManager());
+ TvSingletons.getSingletons(context).getChannelDataManager(),
+ TvSingletons.getSingletons(context).getTvInputManagerHelper());
}
@VisibleForTesting
@@ -148,7 +175,8 @@
Looper looper,
BackendKnobsFlags backendKnobsFlags,
PerformanceMonitor performanceMonitor,
- ChannelDataManager channelDataManager) {
+ ChannelDataManager channelDataManager,
+ TvInputManagerHelper tvInputManagerHelper) {
mContext = context;
mDbExecutor = executor;
mClock = time;
@@ -157,6 +185,7 @@
mBackendKnobsFlags = backendKnobsFlags;
mPerformanceMonitor = performanceMonitor;
mChannelDataManager = channelDataManager;
+ mTvInputManagerHelper = tvInputManagerHelper;
mProgramObserver =
new ContentObserver(mHandler) {
@Override
@@ -205,6 +234,7 @@
// Should be called directly instead of posting MSG_UPDATE_CURRENT_PROGRAMS message
// to the handler. If not, another DB task can be executed before loading current programs.
handleUpdateCurrentPrograms();
+ mHandler.sendEmptyMessage(MSG_UPDATE_CONTENT_RATINGS);
if (mPrefetchEnabled) {
mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
}
@@ -259,17 +289,69 @@
}
}
- public void prefetchChannel(long channelId) {
- if (mCompleteInfoChannelIds.add(channelId)) {
- long startTimeMs =
- Utils.floorTime(
- mClock.currentTimeMillis() - PROGRAM_GUIDE_SNAP_TIME_MS,
- PROGRAM_GUIDE_SNAP_TIME_MS);
- long endTimeMs = startTimeMs + TimeUnit.HOURS.toMillis(getFetchDuration());
+ /**
+ * Prefetch program data if needed.
+ *
+ * @param channelId ID of the channel to prefetch
+ * @param selectedProgramIndex index of selected program.
+ */
+ public void prefetchChannel(long channelId, int selectedProgramIndex) {
+ long startTimeMs =
+ Utils.floorTime(
+ mClock.currentTimeMillis() - PROGRAM_GUIDE_SNAP_TIME_MS,
+ PROGRAM_GUIDE_SNAP_TIME_MS);
+ long programGuideMaxHoursMs =
+ TimeUnit.HOURS.toMillis(mBackendKnobsFlags.programGuideMaxHours());
+ long endTimeMs = 0;
+ if (mMaxFetchHoursMs < programGuideMaxHoursMs
+ && isHorizontalLoadNeeded(startTimeMs, channelId, selectedProgramIndex)) {
+ // Horizontal scrolling needs to load data of further days.
+ mMaxFetchHoursMs = Math.min(programGuideMaxHoursMs, mMaxFetchHoursMs + FETCH_HOURS_MS);
+ mCompleteInfoChannelIds.clear();
+ }
+ // Load max hours complete data for first channel.
+ if (mCompleteInfoChannelIds.isEmpty()) {
+ endTimeMs = startTimeMs + programGuideMaxHoursMs;
+ } else if (!mCompleteInfoChannelIds.contains(channelId)) {
+ endTimeMs = startTimeMs + mMaxFetchHoursMs;
+ }
+ if (endTimeMs > 0) {
+ mCompleteInfoChannelIds.add(channelId);
new SingleChannelPrefetchTask(channelId, startTimeMs, endTimeMs).executeOnDbThread();
}
}
+ public void prefetchChannel(long channelId) {
+ prefetchChannel(channelId, 0);
+ }
+
+ /**
+ * Check if enough data is present for horizontal scroll, otherwise prefetch programs.
+ *
+ * <p>If end time of current program is past {@code BUFFER_HOURS_MS} less than the fetched time
+ * we need to prefetch proceeding programs.
+ *
+ * @param startTimeMs Fetch start time, it is used to get fetch end time.
+ * @param channelId
+ * @param selectedProgramIndex
+ * @return {@code true} If data load is needed, else {@code false}.
+ */
+ private boolean isHorizontalLoadNeeded(
+ long startTimeMs, long channelId, int selectedProgramIndex) {
+ if (mChannelIdProgramCache.containsKey(channelId)) {
+ ArrayList<Program> programs = mChannelIdProgramCache.get(channelId);
+ long marginEndTime = startTimeMs + mMaxFetchHoursMs - BUFFER_HOURS_MS;
+ return programs.size() > selectedProgramIndex &&
+ programs.get(selectedProgramIndex).getEndTimeUtcMillis() > marginEndTime;
+ }
+ return false;
+ }
+
+ public void onChannelTuned(long channelId) {
+ mTunedChannelId = channelId;
+ prefetchChannel(channelId);
+ }
+
/** A Callback interface to receive notification on program data retrieval from DB. */
public interface Callback {
/**
@@ -280,12 +362,10 @@
void onProgramUpdated();
/**
- * Called when we update complete program data of specific channel during scrolling. Data is
- * loaded from DB on request basis.
- *
- * @param channelId
+ * Called when we update program data during scrolling. Data is loaded from DB on request
+ * basis. It loads data based on horizontal scrolling as well.
*/
- void onSingleChannelUpdated(long channelId);
+ void onChannelUpdated();
}
/** Adds the {@link Callback}. */
@@ -312,7 +392,7 @@
} else {
mPrefetchEnabled = false;
cancelPrefetchTask();
- mChannelIdProgramCache.clear();
+ clearChannelInfoMap();
mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM);
}
}
@@ -539,10 +619,7 @@
}
programMap.clear();
- String[] projection =
- mBackendKnobsFlags.enablePartialProgramFetch()
- ? Program.PARTIAL_PROJECTION
- : Program.PROJECTION;
+ String[] projection = ProgramImpl.PARTIAL_PROJECTION;
if (TvProviderUtils.checkSeriesIdColumn(mContext, Programs.CONTENT_URI)) {
if (Utils.isProgramsUri(uri)) {
projection =
@@ -562,10 +639,7 @@
}
return null;
}
- Program program =
- mBackendKnobsFlags.enablePartialProgramFetch()
- ? Program.fromCursorPartialProjection(c)
- : Program.fromCursor(c);
+ Program program = ProgramImpl.fromCursorPartialProjection(c);
if (Program.isDuplicate(program, lastReadProgram)) {
duplicateCount++;
continue;
@@ -575,15 +649,14 @@
ArrayList<Program> programs = programMap.get(program.getChannelId());
if (programs == null) {
programs = new ArrayList<>();
- if (mBackendKnobsFlags.enablePartialProgramFetch()) {
- // To skip already loaded complete data.
- Program currentProgramInfo =
- mChannelIdCurrentProgramMap.get(program.getChannelId());
- if (currentProgramInfo != null
- && Program.isDuplicate(program, currentProgramInfo)) {
- program = currentProgramInfo;
- }
+ // To skip already loaded complete data.
+ Program currentProgramInfo =
+ mChannelIdCurrentProgramMap.get(program.getChannelId());
+ if (currentProgramInfo != null
+ && Program.isDuplicate(program, currentProgramInfo)) {
+ program = currentProgramInfo;
}
+
programMap.put(program.getChannelId(), programs);
}
programs.add(program);
@@ -628,15 +701,12 @@
mLastPrefetchTaskRunMs + PROGRAM_GUIDE_SNAP_TIME_MS,
PROGRAM_GUIDE_SNAP_TIME_MS)
- currentTime;
- // Issue second pre-fetch immediately after the first partial update
- if (mChannelIdProgramCache.isEmpty()) {
- nextMessageDelayedTime = 0;
- }
mChannelIdProgramCache = programs;
- if (mBackendKnobsFlags.enablePartialProgramFetch()) {
- // Since cache has partial data we need to reset the map of complete data.
- mCompleteInfoChannelIds.clear();
- }
+ // Since cache has partial data we need to reset the map of complete data.
+ clearChannelInfoMap();
+ // Get complete projection of tuned channel.
+ prefetchChannel(mTunedChannelId);
+
notifyProgramUpdated();
if (mFromEmptyCacheTimeEvent != null) {
mPerformanceMonitor.stopTimer(
@@ -654,6 +724,11 @@
}
}
+ private void clearChannelInfoMap() {
+ mCompleteInfoChannelIds.clear();
+ mMaxFetchHoursMs = FETCH_HOURS_MS;
+ }
+
private long getFetchDuration() {
if (mChannelIdProgramCache.isEmpty()) {
return Math.max(1L, mBackendKnobsFlags.programGuideInitialFetchHours());
@@ -685,7 +760,7 @@
mDbExecutor,
mContext,
TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs),
- Program.PROJECTION,
+ ProgramImpl.PROJECTION,
null,
null,
SORT_BY_TIME);
@@ -696,7 +771,7 @@
protected ArrayList<Program> onQuery(Cursor c) {
ArrayList<Program> programMap = new ArrayList<>();
while (c.moveToNext()) {
- Program program = Program.fromCursor(c);
+ Program program = ProgramImpl.fromCursor(c);
programMap.add(program);
}
return programMap;
@@ -705,7 +780,7 @@
@Override
protected void onPostExecute(ArrayList<Program> programs) {
mChannelIdProgramCache.put(mChannelId, programs);
- notifySingleChannelUpdated(mChannelId);
+ notifyChannelUpdated();
}
}
@@ -715,9 +790,9 @@
}
}
- private void notifySingleChannelUpdated(long channelId) {
+ private void notifyChannelUpdated() {
for (Callback callback : mCallbacks) {
- callback.onSingleChannelUpdated(channelId);
+ callback.onChannelUpdated();
}
}
@@ -731,10 +806,10 @@
.appendQueryParameter(PARAM_START_TIME, String.valueOf(time))
.appendQueryParameter(PARAM_END_TIME, String.valueOf(time))
.build(),
- Program.PROJECTION,
+ ProgramImpl.PROJECTION,
null,
null,
- SORT_BY_TIME);
+ SORT_BY_CHANNEL_ID);
}
@Override
@@ -747,17 +822,21 @@
if (isCancelled()) {
return programs;
}
- Program program = Program.fromCursor(c);
- if (Program.isDuplicate(program, lastReadProgram)) {
+ Program program = ProgramImpl.fromCursor(c);
+ // Only one program is expected per channel for this query
+ // However, skip overlapping programs from same channel
+ if (Program.sameChannel(program, lastReadProgram)
+ && Program.isOverlapping(program, lastReadProgram)) {
duplicateCount++;
continue;
} else {
lastReadProgram = program;
}
+
programs.add(program);
}
if (duplicateCount > 0) {
- Log.w(TAG, "Found " + duplicateCount + " duplicate programs");
+ Log.w(TAG, "Found " + duplicateCount + " overlapping programs");
}
}
return programs;
@@ -777,9 +856,7 @@
for (Long channelId : removedChannelIds) {
if (mPrefetchEnabled) {
mChannelIdProgramCache.remove(channelId);
- if (mBackendKnobsFlags.enablePartialProgramFetch()) {
- mCompleteInfoChannelIds.remove(channelId);
- }
+ mCompleteInfoChannelIds.remove(channelId);
}
mChannelIdCurrentProgramMap.remove(channelId);
notifyCurrentProgramUpdate(channelId, null);
@@ -797,7 +874,7 @@
mDbExecutor,
mContext,
TvContract.buildProgramsUriForChannel(channelId, time, time),
- Program.PROJECTION,
+ ProgramImpl.PROJECTION,
null,
null,
SORT_BY_TIME);
@@ -808,7 +885,7 @@
public Program onQuery(Cursor c) {
Program program = null;
if (c != null && c.moveToNext()) {
- program = Program.fromCursor(c);
+ program = ProgramImpl.fromCursor(c);
}
return program;
}
@@ -869,6 +946,9 @@
}
break;
}
+ case MSG_UPDATE_CONTENT_RATINGS:
+ mTvInputManagerHelper.getContentRatingsManager().update();
+ break;
default:
// Do nothing
}
@@ -932,7 +1012,7 @@
// Create dummy program which indicates data isn't loaded yet so DB query is required.
private Program createDummyProgram(long startTimeMs, long endTimeMs) {
- return new Program.Builder()
+ return new ProgramImpl.Builder()
.setChannelId(Channel.INVALID_ID)
.setStartTimeUtcMillis(startTimeMs)
.setEndTimeUtcMillis(endTimeMs)
diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/ProgramImpl.java
similarity index 86%
rename from src/com/android/tv/data/Program.java
rename to src/com/android/tv/data/ProgramImpl.java
index b688927..5097e2d 100644
--- a/src/com/android/tv/data/Program.java
+++ b/src/com/android/tv/data/ProgramImpl.java
@@ -32,24 +32,26 @@
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
-import android.util.Log;
-import com.android.tv.common.BuildConfig;
+
import com.android.tv.common.TvContentRatingCache;
import com.android.tv.common.util.CollectionUtils;
import com.android.tv.common.util.CommonUtils;
+import com.android.tv.data.api.BaseProgram;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.util.TvProviderUtils;
import com.android.tv.util.Utils;
import com.android.tv.util.images.ImageLoader;
+
import com.google.common.collect.ImmutableList;
-import java.io.Serializable;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/** A convenience class to create and insert program information entries into the database. */
-public final class Program extends BaseProgram implements Comparable<Program>, Parcelable {
+public final class ProgramImpl extends BaseProgramImpl implements Parcelable, Program {
private static final boolean DEBUG = false;
private static final boolean DEBUG_DUMP_DESCRIPTION = false;
private static final String TAG = "Program";
@@ -179,8 +181,8 @@
return builder.build();
}
- public static Program fromParcel(Parcel in) {
- Program program = new Program();
+ public static ProgramImpl fromParcel(Parcel in) {
+ ProgramImpl program = new ProgramImpl();
program.mId = in.readLong();
program.mPackageName = in.readString();
program.mChannelId = in.readLong();
@@ -219,7 +221,7 @@
new Parcelable.Creator<Program>() {
@Override
public Program createFromParcel(Parcel in) {
- return Program.fromParcel(in);
+ return ProgramImpl.fromParcel(in);
}
@Override
@@ -251,19 +253,21 @@
private ImmutableList<TvContentRating> mContentRatings;
private boolean mRecordingProhibited;
- private Program() {
+ private ProgramImpl() {
// Do nothing.
}
+ @Override
public long getId() {
return mId;
}
- /** Returns the package name of this program. */
+ @Override
public String getPackageName() {
return mPackageName;
}
+ @Override
public long getChannelId() {
return mChannelId;
}
@@ -274,11 +278,6 @@
return mChannelId >= 0;
}
- /** Returns {@code true} if the program is valid and {@code false} otherwise. */
- public static boolean isProgramValid(Program program) {
- return program != null && program.isValid();
- }
-
@Override
public String getTitle() {
return mTitle;
@@ -302,6 +301,11 @@
}
@Override
+ public String getSeasonTitle() {
+ return mSeasonTitle;
+ }
+
+ @Override
public String getEpisodeNumber() {
return mEpisodeNumber;
}
@@ -316,6 +320,7 @@
return mEndTimeUtcMillis;
}
+ @Override
public String getDurationString(Context context) {
// TODO(b/71717446): expire the calculated string
if (mDurationString == null) {
@@ -341,15 +346,17 @@
return mLongDescription;
}
+ @Override
public int getVideoWidth() {
return mVideoWidth;
}
+ @Override
public int getVideoHeight() {
return mVideoHeight;
}
- /** Returns the list of Critic Scores for this program */
+ @Override
@Nullable
public List<CriticScore> getCriticScores() {
return mCriticScores;
@@ -371,12 +378,12 @@
return mThumbnailUri;
}
- /** Returns {@code true} if the recording of this program is prohibited. */
+ @Override
public boolean isRecordingProhibited() {
return mRecordingProhibited;
}
- /** Returns array of canonical genres for this program. This is expected to be called rarely. */
+ @Override
@Nullable
public String[] getCanonicalGenres() {
if (mCanonicalGenreIds == null) {
@@ -395,7 +402,7 @@
return mCanonicalGenreIds;
}
- /** Returns if this program has the genre. */
+ @Override
public boolean hasGenre(int genreId) {
if (genreId == GenreItems.ID_ALL_CHANNELS) {
return true;
@@ -436,11 +443,11 @@
@Override
public boolean equals(Object other) {
- if (!(other instanceof Program)) {
+ if (!(other instanceof ProgramImpl)) {
return false;
}
// Compare all the properties because program ID can be invalid for the dummy programs.
- Program program = (Program) other;
+ ProgramImpl program = (ProgramImpl) other;
return Objects.equals(mPackageName, program.mPackageName)
&& mChannelId == program.mChannelId
&& mStartTimeUtcMillis == program.mStartTimeUtcMillis
@@ -464,7 +471,7 @@
@Override
public int compareTo(@NonNull Program other) {
- return Long.compare(mStartTimeUtcMillis, other.mStartTimeUtcMillis);
+ return Long.compare(mStartTimeUtcMillis, other.getStartTimeUtcMillis());
}
@Override
@@ -516,7 +523,7 @@
}
/**
- * Translates a {@link Program} to {@link ContentValues} that are ready to be written into
+ * Translates a {@link ProgramImpl} to {@link ContentValues} that are ready to be written into
* Database.
*/
@SuppressLint("InlinedApi")
@@ -595,37 +602,37 @@
return;
}
- mId = other.mId;
- mPackageName = other.mPackageName;
- mChannelId = other.mChannelId;
- mTitle = other.mTitle;
- mSeriesId = other.mSeriesId;
- mEpisodeTitle = other.mEpisodeTitle;
- mSeasonNumber = other.mSeasonNumber;
- mSeasonTitle = other.mSeasonTitle;
- mEpisodeNumber = other.mEpisodeNumber;
- mStartTimeUtcMillis = other.mStartTimeUtcMillis;
- mEndTimeUtcMillis = other.mEndTimeUtcMillis;
+ mId = other.getId();
+ mPackageName = other.getPackageName();
+ mChannelId = other.getChannelId();
+ mTitle = other.getTitle();
+ mSeriesId = other.getSeriesId();
+ mEpisodeTitle = other.getEpisodeTitle();
+ mSeasonNumber = other.getSeasonNumber();
+ mSeasonTitle = other.getSeasonTitle();
+ mEpisodeNumber = other.getEpisodeNumber();
+ mStartTimeUtcMillis = other.getStartTimeUtcMillis();
+ mEndTimeUtcMillis = other.getEndTimeUtcMillis();
mDurationString = null; // Recreate Duration when needed.
- mDescription = other.mDescription;
- mLongDescription = other.mLongDescription;
- mVideoWidth = other.mVideoWidth;
- mVideoHeight = other.mVideoHeight;
- mCriticScores = other.mCriticScores;
- mPosterArtUri = other.mPosterArtUri;
- mThumbnailUri = other.mThumbnailUri;
- mCanonicalGenreIds = other.mCanonicalGenreIds;
- mContentRatings = other.mContentRatings;
- mRecordingProhibited = other.mRecordingProhibited;
+ mDescription = other.getDescription();
+ mLongDescription = other.getLongDescription();
+ mVideoWidth = other.getVideoWidth();
+ mVideoHeight = other.getVideoHeight();
+ mCriticScores = other.getCriticScores();
+ mPosterArtUri = other.getPosterArtUri();
+ mThumbnailUri = other.getThumbnailUri();
+ mCanonicalGenreIds = other.getCanonicalGenreIds();
+ mContentRatings = other.getContentRatings();
+ mRecordingProhibited = other.isRecordingProhibited();
}
/** A Builder for the Program class */
public static final class Builder {
- private final Program mProgram;
+ private final ProgramImpl mProgram;
/** Creates a Builder for this Program class */
public Builder() {
- mProgram = new Program();
+ mProgram = new ProgramImpl();
// Fill initial data.
mProgram.mPackageName = null;
mProgram.mChannelId = Channel.INVALID_ID;
@@ -650,7 +657,7 @@
*/
@VisibleForTesting
public Builder(Program other) {
- mProgram = new Program();
+ mProgram = new ProgramImpl();
mProgram.copyFrom(other);
}
@@ -906,7 +913,7 @@
*
* @return the Program object constructed
*/
- public Program build() {
+ public ProgramImpl build() {
// Generate the series ID for the episodic program of other TV input.
if (TextUtils.isEmpty(mProgram.mTitle)) {
// If title is null, series cannot be generated for this program.
@@ -916,17 +923,13 @@
// If series ID is not set, generate it for the episodic program of other TV input.
setSeriesId(BaseProgram.generateSeriesId(mProgram.mPackageName, mProgram.mTitle));
}
- Program program = new Program();
+ ProgramImpl program = new ProgramImpl();
program.copyFrom(mProgram);
return program;
}
}
- /**
- * Prefetches the program poster art.
- *
- * <p>
- */
+ @Override
public void prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight) {
if (mPosterArtUri == null) {
return;
@@ -934,20 +937,13 @@
ImageLoader.prefetchBitmap(context, mPosterArtUri, posterArtWidth, posterArtHeight);
}
- /**
- * Loads the program poster art and returns it via {@code callback}.
- *
- * <p>Note that it may directly call {@code callback} if the program poster art already is
- * loaded.
- *
- * @return {@code true} if the load is complete and the callback is executed.
- */
+ @Override
@UiThread
public boolean loadPosterArt(
Context context,
int posterArtWidth,
int posterArtHeight,
- ImageLoader.ImageLoaderCallback callback) {
+ ImageLoader.ImageLoaderCallback<?> callback) {
if (mPosterArtUri == null) {
return false;
}
@@ -955,24 +951,9 @@
context, mPosterArtUri, posterArtWidth, posterArtHeight, callback);
}
- public static boolean isDuplicate(Program p1, Program p2) {
- if (p1 == null || p2 == null) {
- return false;
- }
- boolean isDuplicate =
- p1.getChannelId() == p2.getChannelId()
- && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis()
- && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis();
- if (DEBUG && BuildConfig.ENG && isDuplicate) {
- Log.w(
- TAG,
- "Duplicate programs detected! - \""
- + p1.getTitle()
- + "\" and \""
- + p2.getTitle()
- + "\"");
- }
- return isDuplicate;
+ @Override
+ public Parcelable toParcelable() {
+ return this;
}
@Override
@@ -1009,54 +990,4 @@
}
out.writeByte((byte) (mRecordingProhibited ? 1 : 0));
}
-
- /** Holds one type of critic score and its source. */
- public static final class CriticScore implements Serializable, Parcelable {
- /** The source of the rating. */
- public final String source;
- /** The score. */
- public final String score;
- /** The url of the logo image */
- public final String logoUrl;
-
- public static final Parcelable.Creator<CriticScore> CREATOR =
- new Parcelable.Creator<CriticScore>() {
- @Override
- public CriticScore createFromParcel(Parcel in) {
- String source = in.readString();
- String score = in.readString();
- String logoUri = in.readString();
- return new CriticScore(source, score, logoUri);
- }
-
- @Override
- public CriticScore[] newArray(int size) {
- return new CriticScore[size];
- }
- };
-
- /**
- * Constructor for this class.
- *
- * @param source the source of the rating
- * @param score the score
- */
- public CriticScore(String source, String score, String logoUrl) {
- this.source = source;
- this.score = score;
- this.logoUrl = logoUrl;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel out, int i) {
- out.writeString(source);
- out.writeString(score);
- out.writeString(logoUrl);
- }
- }
}
diff --git a/src/com/android/tv/data/api/BaseProgram.java b/src/com/android/tv/data/api/BaseProgram.java
new file mode 100644
index 0000000..8acaf79
--- /dev/null
+++ b/src/com/android/tv/data/api/BaseProgram.java
@@ -0,0 +1,141 @@
+package com.android.tv.data.api;
+
+import android.content.Context;
+import android.media.tv.TvContentRating;
+import android.support.annotation.Nullable;
+import com.google.common.collect.ImmutableList;
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * Base class for {@link com.android.tv.data.Program} and {@link
+ * com.android.tv.dvr.data.RecordedProgram}.
+ */
+public interface BaseProgram {
+
+ /**
+ * Comparator used to compare {@link BaseProgram} according to its season and episodes number.
+ * If a program's season or episode number is null, it will be consider "smaller" than programs
+ * with season or episode numbers.
+ */
+ Comparator<BaseProgram> EPISODE_COMPARATOR = new EpisodeComparator(false);
+ /**
+ * Comparator used to compare {@link BaseProgram} according to its season and episodes number
+ * with season numbers in a reversed order. If a program's season or episode number is null, it
+ * will be consider "smaller" than programs with season or episode numbers.
+ */
+ Comparator<BaseProgram> SEASON_REVERSED_EPISODE_COMPARATOR = new EpisodeComparator(true);
+
+ String COLUMN_SERIES_ID = "series_id";
+ String COLUMN_STATE = "state";
+
+ /** Compares two strings represent season numbers or episode numbers of programs. */
+ static int numberCompare(String s1, String s2) {
+ if (Objects.equals(s1, s2)) {
+ return 0;
+ } else if (s1 == null) {
+ return -1;
+ } else if (s2 == null) {
+ return 1;
+ } else if (s1.equals(s2)) {
+ return 0;
+ }
+ try {
+ return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2));
+ } catch (NumberFormatException e) {
+ return s1.compareTo(s2);
+ }
+ }
+
+ /** Generates the series ID for the other inputs than the tuner TV input. */
+ static String generateSeriesId(String packageName, String title) {
+ return packageName + "/" + title;
+ }
+
+ /** Returns ID of the program. */
+ long getId();
+
+ /** Returns the title of the program. */
+ String getTitle();
+
+ /** Returns the episode title. */
+ String getEpisodeTitle();
+
+ /** Returns the displayed title of the program episode. */
+ @Nullable
+ String getEpisodeDisplayTitle(Context context);
+
+ /**
+ * Returns the content description of the program episode, suitable for being spoken by an
+ * accessibility service.
+ */
+ String getEpisodeContentDescription(Context context);
+
+ /** Returns the description of the program. */
+ String getDescription();
+
+ /** Returns the long description of the program. */
+ String getLongDescription();
+
+ /** Returns the start time of the program in Milliseconds. */
+ long getStartTimeUtcMillis();
+
+ /** Returns the end time of the program in Milliseconds. */
+ long getEndTimeUtcMillis();
+
+ /** Returns the duration of the program in Milliseconds. */
+ long getDurationMillis();
+
+ /** Returns the series ID. */
+ @Nullable
+ String getSeriesId();
+
+ /** Returns the season number. */
+ String getSeasonNumber();
+
+ /** Returns the episode number. */
+ String getEpisodeNumber();
+
+ /** Returns URI of the program's poster. */
+ String getPosterArtUri();
+
+ /** Returns URI of the program's thumbnail. */
+ String getThumbnailUri();
+
+ /** Returns the array of the ID's of the canonical genres. */
+ int[] getCanonicalGenreIds();
+
+ /** Returns the array of content ratings. */
+ ImmutableList<TvContentRating> getContentRatings();
+
+ /** Returns channel's ID of the program. */
+ long getChannelId();
+
+ /** Returns if the program is valid. */
+ boolean isValid();
+
+ /** Checks whether the program is episodic or not. */
+ boolean isEpisodic();
+
+ /** Generates the series ID for the other inputs than the tuner TV input. */
+ class EpisodeComparator implements Comparator<BaseProgram> {
+ private final boolean mReversedSeason;
+
+ EpisodeComparator(boolean reversedSeason) {
+ mReversedSeason = reversedSeason;
+ }
+
+ @Override
+ public int compare(BaseProgram lhs, BaseProgram rhs) {
+ if (lhs == rhs) {
+ return 0;
+ }
+ int seasonNumberCompare = numberCompare(lhs.getSeasonNumber(), rhs.getSeasonNumber());
+ if (seasonNumberCompare != 0) {
+ return mReversedSeason ? -seasonNumberCompare : seasonNumberCompare;
+ } else {
+ return numberCompare(lhs.getEpisodeNumber(), rhs.getEpisodeNumber());
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/data/api/Program.java b/src/com/android/tv/data/api/Program.java
new file mode 100644
index 0000000..f2221f6
--- /dev/null
+++ b/src/com/android/tv/data/api/Program.java
@@ -0,0 +1,141 @@
+package com.android.tv.data.api;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+
+import com.android.tv.util.images.ImageLoader;
+
+import java.io.Serializable;
+import java.util.List;
+
+/** A convenience interface to create and insert program information entries into the database. */
+public interface Program extends BaseProgram, Comparable<Program> {
+
+ /** Returns {@code true} if the program is valid and {@code false} otherwise. */
+ static boolean isProgramValid(Program program) {
+ return program != null && program.isValid();
+ }
+
+ static boolean isDuplicate(Program p1, Program p2) {
+ if (p1 == null || p2 == null) {
+ return false;
+ }
+ return p1.getChannelId() == p2.getChannelId()
+ && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis()
+ && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis();
+ }
+
+ /** True if the start or end times overlap. */
+ static boolean isOverlapping(@Nullable Program p1, @Nullable Program p2) {
+ return p1 != null
+ && p2 != null
+ && p1.getStartTimeUtcMillis() < p2.getEndTimeUtcMillis()
+ && p1.getEndTimeUtcMillis() > p2.getStartTimeUtcMillis();
+ }
+
+ /** True if the channels IDs are the same. */
+ static boolean sameChannel(@Nullable Program p1, @Nullable Program p2) {
+ return p1 != null && p2 != null && p1.getChannelId() == p2.getChannelId();
+ }
+
+ /** Returns the package name of this program. */
+ String getPackageName();
+
+ /** Returns the season title */
+ String getSeasonTitle();
+
+ /** Gets the localized duration of the program */
+ String getDurationString(Context context);
+
+ int getVideoWidth();
+
+ int getVideoHeight();
+
+ /** Returns the list of Critic Scores for this program */
+ @Nullable
+ List<CriticScore> getCriticScores();
+
+ /** Returns {@code true} if the recording of this program is prohibited. */
+ boolean isRecordingProhibited();
+
+ /** Returns array of canonical genres for this program. This is expected to be called rarely. */
+ @Nullable
+ String[] getCanonicalGenres();
+
+ /** Returns if this program has the genre. */
+ boolean hasGenre(int genreId);
+
+ /** Prefetch the program poster art. */
+ void prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight);
+
+ /**
+ * Loads the program poster art and returns it via {@code callback}.
+ *
+ * <p>Note that it may directly call {@code callback} if the program poster art already is
+ * loaded.
+ *
+ * @return {@code true} if the load is complete and the callback is executed.
+ */
+ @UiThread
+ boolean loadPosterArt(
+ Context context,
+ int posterArtWidth,
+ int posterArtHeight,
+ ImageLoader.ImageLoaderCallback<?> callback);
+
+ /** Returns a {@link Parcelable} representation of this instance. */
+ Parcelable toParcelable();
+
+ /** Holds one type of critic score and its source. */
+ final class CriticScore implements Serializable, Parcelable {
+ /** The source of the rating. */
+ public final String source;
+ /** The score. */
+ public final String score;
+ /** The url of the logo image */
+ public final String logoUrl;
+
+ public static final Creator<CriticScore> CREATOR =
+ new Creator<CriticScore>() {
+ @Override
+ public CriticScore createFromParcel(Parcel in) {
+ String source = in.readString();
+ String score = in.readString();
+ String logoUri = in.readString();
+ return new CriticScore(source, score, logoUri);
+ }
+
+ @Override
+ public CriticScore[] newArray(int size) {
+ return new CriticScore[size];
+ }
+ };
+
+ /**
+ * Constructor for this class.
+ *
+ * @param source the source of the rating
+ * @param score the score
+ */
+ public CriticScore(String source, String score, String logoUrl) {
+ this.source = source;
+ this.score = score;
+ this.logoUrl = logoUrl;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int i) {
+ out.writeString(source);
+ out.writeString(score);
+ out.writeString(logoUrl);
+ }
+ }
+}
diff --git a/src/com/android/tv/data/epg/EpgFetchHelper.java b/src/com/android/tv/data/epg/EpgFetchHelper.java
index 3843ca9..4e88911 100644
--- a/src/com/android/tv/data/epg/EpgFetchHelper.java
+++ b/src/com/android/tv/data/epg/EpgFetchHelper.java
@@ -28,12 +28,15 @@
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.tv.common.CommonConstants;
import com.android.tv.common.util.Clock;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.features.TvFeatures;
import com.android.tv.util.TvProviderUtils;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -106,7 +109,7 @@
ops.add(
ContentProviderOperation.newUpdate(
TvContract.buildProgramUri(oldProgram.getId()))
- .withValues(Program.toContentValues(newProgram, context))
+ .withValues(ProgramImpl.toContentValues(newProgram, context))
.build());
oldProgramsIndex++;
newProgramsIndex++;
@@ -132,7 +135,7 @@
if (addNewProgram) {
ops.add(
ContentProviderOperation.newInsert(Programs.CONTENT_URI)
- .withValues(Program.toContentValues(newProgram, context))
+ .withValues(ProgramImpl.toContentValues(newProgram, context))
.build());
}
// Throttle the batch operation not to cause TransactionTooLargeException.
@@ -199,7 +202,7 @@
@WorkerThread
private static List<Program> queryPrograms(
Context context, long channelId, long startTimeMs, long endTimeMs) {
- String[] projection = Program.PROJECTION;
+ String[] projection = ProgramImpl.PROJECTION;
if (TvProviderUtils.checkSeriesIdColumn(context, Programs.CONTENT_URI)) {
projection =
TvProviderUtils.addExtraColumnsToProjection(
@@ -219,7 +222,7 @@
}
ArrayList<Program> programs = new ArrayList<>();
while (c.moveToNext()) {
- programs.add(Program.fromCursor(c));
+ programs.add(ProgramImpl.fromCursor(c));
}
return programs;
}
diff --git a/src/com/android/tv/data/epg/EpgFetchService.java b/src/com/android/tv/data/epg/EpgFetchService.java
index aa4f358..cfa79cb 100644
--- a/src/com/android/tv/data/epg/EpgFetchService.java
+++ b/src/com/android/tv/data/epg/EpgFetchService.java
@@ -18,22 +18,24 @@
import android.app.job.JobParameters;
import android.app.job.JobService;
+
import com.android.tv.Starter;
-import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
+import dagger.android.AndroidInjection;
+
+import javax.inject.Inject;
+
/** JobService to Fetch EPG data. */
public class EpgFetchService extends JobService {
- private EpgFetcher mEpgFetcher;
- private ChannelDataManager mChannelDataManager;
+ @Inject EpgFetcher mEpgFetcher;
+ @Inject ChannelDataManager mChannelDataManager;
@Override
public void onCreate() {
+ AndroidInjection.inject(this);
super.onCreate();
Starter.start(this);
- TvSingletons tvSingletons = TvSingletons.getSingletons(getApplicationContext());
- mEpgFetcher = tvSingletons.getEpgFetcher();
- mChannelDataManager = tvSingletons.getChannelDataManager();
}
@Override
diff --git a/src/com/android/tv/data/epg/EpgFetcherImpl.java b/src/com/android/tv/data/epg/EpgFetcherImpl.java
index b191421..be53099 100644
--- a/src/com/android/tv/data/epg/EpgFetcherImpl.java
+++ b/src/com/android/tv/data/epg/EpgFetcherImpl.java
@@ -38,10 +38,12 @@
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.BuildConfig;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.buildtype.HasBuildType;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.common.util.Clock;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.common.util.LocationUtils;
@@ -52,17 +54,20 @@
import com.android.tv.data.ChannelImpl;
import com.android.tv.data.ChannelLogoFetcher;
import com.android.tv.data.Lineup;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.features.TvFeatures;
import com.android.tv.perf.EventNames;
import com.android.tv.perf.PerformanceMonitor;
import com.android.tv.perf.TimerEvent;
import com.android.tv.util.Utils;
+
import com.google.android.tv.partner.support.EpgInput;
import com.google.android.tv.partner.support.EpgInputs;
import com.google.common.collect.ImmutableSet;
+
import com.android.tv.common.flags.BackendKnobsFlags;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -73,6 +78,8 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import javax.inject.Inject;
+
/**
* The service class to fetch EPG routinely or on-demand during channel scanning
*
@@ -130,31 +137,9 @@
private Clock mClock;
- public static EpgFetcher create(Context context) {
- context = context.getApplicationContext();
- TvSingletons tvSingletons = TvSingletons.getSingletons(context);
- ChannelDataManager channelDataManager = tvSingletons.getChannelDataManager();
- PerformanceMonitor performanceMonitor = tvSingletons.getPerformanceMonitor();
- EpgReader epgReader = tvSingletons.providesEpgReader().get();
- Clock clock = tvSingletons.getClock();
- EpgInputWhiteList epgInputWhiteList =
- new EpgInputWhiteList(tvSingletons.getCloudEpgFlags());
- BackendKnobsFlags backendKnobsFlags = tvSingletons.getBackendKnobs();
- HasBuildType.BuildType buildType = tvSingletons.getBuildType();
- return new EpgFetcherImpl(
- context,
- epgInputWhiteList,
- channelDataManager,
- epgReader,
- performanceMonitor,
- clock,
- backendKnobsFlags,
- buildType);
- }
-
- @VisibleForTesting
- EpgFetcherImpl(
- Context context,
+ @Inject
+ public EpgFetcherImpl(
+ @ApplicationContext Context context,
EpgInputWhiteList epgInputWhiteList,
ChannelDataManager channelDataManager,
EpgReader epgReader,
@@ -485,7 +470,7 @@
@WorkerThread
private void batchUpdateEpg(Map<EpgReader.EpgChannel, Collection<Program>> allPrograms) {
for (Map.Entry<EpgReader.EpgChannel, Collection<Program>> entry : allPrograms.entrySet()) {
- List<Program> programs = new ArrayList(entry.getValue());
+ List<Program> programs = new ArrayList<>(entry.getValue());
if (programs == null) {
continue;
}
@@ -604,6 +589,7 @@
? ((Integer) REASON_CLOUD_EPG_FAILURE)
: anyCloudEpgSuccess ? null : builtInResult;
}
+ clearUnusedLineups(null);
return builtInResult;
} finally {
TrafficStats.setThreadStatsTag(oldTag);
diff --git a/src/com/android/tv/data/epg/EpgInputWhiteList.java b/src/com/android/tv/data/epg/EpgInputWhiteList.java
index 24b4fe3..4a5f98b 100644
--- a/src/com/android/tv/data/epg/EpgInputWhiteList.java
+++ b/src/com/android/tv/data/epg/EpgInputWhiteList.java
@@ -21,13 +21,14 @@
import android.text.TextUtils;
import android.util.Log;
import com.android.tv.common.BuildConfig;
-import com.android.tv.common.experiments.Experiments;
import com.android.tv.common.flags.CloudEpgFlags;
+import com.android.tv.common.flags.LegacyFlags;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import javax.inject.Inject;
/** Checks if a package or a input is white listed. */
public final class EpgInputWhiteList {
@@ -36,6 +37,7 @@
private static final String QA_DEV_INPUTS =
"com.example.partnersupportsampletvinput/.SampleTvInputService,"
+ "com.android.tv.tuner.sample.dvb/.tvinput.SampleDvbTunerTvInputService";
+ private final LegacyFlags mLegacyFlags;
/** Returns the package portion of a inputId */
@Nullable
@@ -43,10 +45,12 @@
return inputId == null ? null : inputId.substring(0, inputId.indexOf("/"));
}
- private final CloudEpgFlags cloudEpgFlags;
+ private final CloudEpgFlags mCloudEpgFlags;
- public EpgInputWhiteList(CloudEpgFlags cloudEpgFlags) {
- this.cloudEpgFlags = cloudEpgFlags;
+ @Inject
+ public EpgInputWhiteList(CloudEpgFlags cloudEpgFlags, LegacyFlags legacyFlags) {
+ mCloudEpgFlags = cloudEpgFlags;
+ mLegacyFlags = legacyFlags;
}
public boolean isInputWhiteListed(String inputId) {
@@ -71,8 +75,8 @@
}
private Set<String> getWhiteListedInputs() {
- Set<String> result = toInputSet(cloudEpgFlags.thirdPartyEpgInputsCsv());
- if (BuildConfig.ENG || Experiments.ENABLE_QA_FEATURES.get()) {
+ Set<String> result = toInputSet(mCloudEpgFlags.thirdPartyEpgInputsCsv());
+ if (BuildConfig.ENG || mLegacyFlags.enableQaFeatures()) {
HashSet<String> moreInputs = new HashSet<>(toInputSet(QA_DEV_INPUTS));
if (result.isEmpty()) {
result = moreInputs;
diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java
index c9fcd97..8c0e3f0 100644
--- a/src/com/android/tv/data/epg/EpgReader.java
+++ b/src/com/android/tv/data/epg/EpgReader.java
@@ -19,11 +19,14 @@
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
+
import com.android.tv.data.Lineup;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.SeriesInfo;
+
import com.google.auto.value.AutoValue;
+
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -36,8 +39,8 @@
/** Value class that holds a EpgChannelId and its corresponding {@link Channel} */
@AutoValue
abstract class EpgChannel {
- public static EpgChannel createEpgChannel(Channel channel, String epgChannelId,
- boolean dbUpdateNeeded) {
+ public static EpgChannel createEpgChannel(
+ Channel channel, String epgChannelId, boolean dbUpdateNeeded) {
return new AutoValue_EpgReader_EpgChannel(channel, epgChannelId, dbUpdateNeeded);
}
diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java
index 3b00148..19bf786 100644
--- a/src/com/android/tv/data/epg/StubEpgReader.java
+++ b/src/com/android/tv/data/epg/StubEpgReader.java
@@ -16,21 +16,25 @@
package com.android.tv.data.epg;
-import android.content.Context;
import android.support.annotation.NonNull;
+
import com.android.tv.data.Lineup;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.SeriesInfo;
+
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import javax.inject.Inject;
+
/** A stub class to read EPG. */
public class StubEpgReader implements EpgReader {
- public StubEpgReader(@SuppressWarnings("unused") Context context) {}
+ @Inject
+ public StubEpgReader() {}
@Override
public boolean isAvailable() {
diff --git a/src/com/android/tv/dialog/PinDialogFragment.java b/src/com/android/tv/dialog/PinDialogFragment.java
index 8730809..c714558 100644
--- a/src/com/android/tv/dialog/PinDialogFragment.java
+++ b/src/com/android/tv/dialog/PinDialogFragment.java
@@ -18,6 +18,7 @@
import android.app.ActivityManager;
import android.app.Dialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.media.tv.TvContentRating;
@@ -33,10 +34,13 @@
import android.widget.TextView;
import android.widget.Toast;
import com.android.tv.R;
-import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dialog.picker.PinPicker;
+import com.android.tv.dialog.picker.TvPinPicker;
+import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.TvSettings;
+import dagger.android.AndroidInjection;
+import com.android.tv.common.flags.UiFlags;
+import javax.inject.Inject;
public class PinDialogFragment extends SafeDismissDialogFragment {
private static final String TAG = "PinDialogFragment";
@@ -80,7 +84,8 @@
private TextView mWrongPinView;
private View mEnterPinView;
private TextView mTitleView;
- private PinPicker mPicker;
+
+ private TvPinPicker mTvPinPicker;
private SharedPreferences mSharedPreferences;
private String mPrevPin;
private String mPin;
@@ -88,6 +93,8 @@
private int mWrongPinCount;
private long mDisablePinUntil;
private final Handler mHandler = new Handler();
+ @Inject TvInputManagerHelper mTvInputManagerHelper;
+ @Inject UiFlags mUiFlags;
public static PinDialogFragment create(int type) {
return create(type, null);
@@ -103,6 +110,12 @@
}
@Override
+ public void onAttach(Context context) {
+ AndroidInjection.inject(this);
+ super.onAttach(context);
+ }
+
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRequestType = getArguments().getInt(ARGS_TYPE, PIN_DIALOG_TYPE_ENTER_PIN);
@@ -154,8 +167,8 @@
mWrongPinView = (TextView) v.findViewById(R.id.wrong_pin);
mEnterPinView = v.findViewById(R.id.enter_pin);
mTitleView = (TextView) mEnterPinView.findViewById(R.id.title);
- mPicker = v.findViewById(R.id.pin_picker);
- mPicker.setOnClickListener(
+ mTvPinPicker = v.findViewById(R.id.tv_pin_picker);
+ mTvPinPicker.setOnClickListener(
view -> {
String pin = getPinInput();
if (!TextUtils.isEmpty(pin)) {
@@ -183,8 +196,7 @@
mTitleView.setText(
getString(
R.string.pin_enter_unlock_dvr,
- TvSingletons.getSingletons(getContext())
- .getTvInputManagerHelper()
+ mTvInputManagerHelper
.getContentRatingsManager()
.getDisplayNameForRating(tvContentRating)));
}
@@ -204,7 +216,8 @@
if (mType != PIN_DIALOG_TYPE_NEW_PIN) {
updateWrongPin();
}
- mPicker.requestFocus();
+
+ mTvPinPicker.requestFocus();
return v;
}
@@ -338,11 +351,11 @@
}
private String getPinInput() {
- return mPicker.getPinInput();
+ return mTvPinPicker.getPin();
}
private void resetPinInput() {
- mPicker.resetPinInput();
+ mTvPinPicker.resetPin();
}
/**
diff --git a/src/com/android/tv/dialog/picker/PinPicker.java b/src/com/android/tv/dialog/picker/PinPicker.java
deleted file mode 100644
index f501dfd..0000000
--- a/src/com/android/tv/dialog/picker/PinPicker.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2018 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.dialog.picker;
-
-import android.content.Context;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.support.v17.leanback.widget.picker.Picker;
-import android.support.v17.leanback.widget.picker.PickerColumn;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import java.util.ArrayList;
-import java.util.List;
-
-/** 4 digit picker */
-public final class PinPicker extends Picker {
- // TODO(b/116144491): use leanback pin picker.
-
- private final List<PickerColumn> mPickers = new ArrayList<>();
- private OnClickListener mOnClickListener;
-
- // the version of picker I link to does not have this constructor
- public PinPicker(Context context, AttributeSet attributeSet) {
- this(context, attributeSet, 0);
- }
-
- public PinPicker(Context context, AttributeSet attributeSet, int defStyleAttr) {
- super(context, attributeSet, defStyleAttr);
-
- for (int i = 0; i < 4; i++) {
- PickerColumn pickerColumn = new PickerColumn();
- pickerColumn.setMinValue(0);
- pickerColumn.setMaxValue(9);
- pickerColumn.setLabelFormat("%d");
- mPickers.add(pickerColumn);
- }
- setSeparator(" ");
- setColumns(mPickers);
- setActivated(true);
- setFocusable(true);
- super.setOnClickListener(this::onClick);
- }
-
- public String getPinInput() {
- String result = "";
- try {
- for (PickerColumn column : mPickers) {
-
- result += column.getCurrentValue();
- }
- } catch (IllegalStateException e) {
- result = "";
- }
- return result;
- }
-
- @Override
- public void setOnClickListener(@Nullable OnClickListener l) {
- mOnClickListener = l;
- }
-
- private void onClick(View v) {
- int selectedColumn = getSelectedColumn();
- int nextColumn = selectedColumn + 1;
- // Only call the click listener if we are on the last column
- // Otherwise move to the next column
- if (nextColumn == getColumnsCount()) {
- if (mOnClickListener != null) {
- mOnClickListener.onClick(v);
- }
- } else {
- setSelectedColumn(nextColumn);
- onRequestFocusInDescendants(ViewGroup.FOCUS_FORWARD, null);
- }
- }
-
- public void resetPinInput() {
- setActivated(false);
- for (int i = 0; i < 4; i++) {
- setColumnValue(i, 0, true);
- }
- setSelectedColumn(0);
- setActivated(true); // This resets the focus
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- int keyCode = event.getKeyCode();
- int digit = digitFromKeyCode(keyCode);
- if (digit != -1) {
- int selectedColumn = getSelectedColumn();
- setColumnValue(selectedColumn, digit, false);
- int nextColumn = selectedColumn + 1;
- if (nextColumn < getColumnsCount()) {
- setSelectedColumn(nextColumn);
- onRequestFocusInDescendants(ViewGroup.FOCUS_FORWARD, null);
- } else {
- callOnClick();
- }
- return true;
- }
- }
- return super.dispatchKeyEvent(event);
- }
-
- @VisibleForTesting
- static int digitFromKeyCode(int keyCode) {
- if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
- return keyCode - KeyEvent.KEYCODE_0;
- } else if (keyCode >= KeyEvent.KEYCODE_NUMPAD_0 && keyCode <= KeyEvent.KEYCODE_NUMPAD_9) {
- return keyCode - KeyEvent.KEYCODE_NUMPAD_0;
- }
- return -1;
- }
-}
diff --git a/src/com/android/tv/dialog/picker/TvPinPicker.java b/src/com/android/tv/dialog/picker/TvPinPicker.java
new file mode 100644
index 0000000..064b7f0
--- /dev/null
+++ b/src/com/android/tv/dialog/picker/TvPinPicker.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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.dialog.picker;
+
+import static android.content.Context.ACCESSIBILITY_SERVICE;
+
+import android.content.Context;
+import androidx.leanback.widget.picker.PinPicker;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityManager;
+
+/** 4 digit picker */
+public final class TvPinPicker extends PinPicker {
+
+ private boolean mSkipPerformClick = true;
+ private boolean mIsAccessibilityEnabled = false;
+
+ public TvPinPicker(Context context, AttributeSet attributeSet) {
+ this(context, attributeSet, 0);
+ }
+
+ public TvPinPicker(Context context, AttributeSet attributeSet, int defStyleAttr) {
+ super(context, attributeSet, defStyleAttr);
+ setActivated(true);
+ AccessibilityManager am =
+ (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
+ mIsAccessibilityEnabled = am.isEnabled();
+ }
+
+ @Override
+ public boolean performClick() {
+ // (b/120096347) Skip first click when talkback is enabled
+ if (mSkipPerformClick && mIsAccessibilityEnabled) {
+ mSkipPerformClick = false;
+ /* Force focus to next value */
+ setColumnValue(getSelectedColumn(), 1, true);
+ return false;
+ }
+ return super.performClick();
+ }
+}
diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java
index 0053650..3e26a23 100644
--- a/src/com/android/tv/dvr/DvrDataManagerImpl.java
+++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java
@@ -37,8 +37,10 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Range;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.recording.RecordingStorageStatusManager.OnStorageMountChangedListener;
import com.android.tv.common.util.Clock;
@@ -48,6 +50,7 @@
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.provider.DvrDatabaseHelper;
import com.android.tv.dvr.provider.DvrDbFuture.AddScheduleFuture;
import com.android.tv.dvr.provider.DvrDbFuture.AddSeriesRecordingFuture;
import com.android.tv.dvr.provider.DvrDbFuture.DeleteScheduleFuture;
@@ -60,11 +63,14 @@
import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.AsyncDbTask.AsyncRecordedProgramQueryTask;
+import com.android.tv.util.AsyncDbTask.DbExecutor;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.TvUriMatcher;
+
import com.google.common.base.Predicate;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -76,9 +82,13 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/** DVR Data manager to handle recordings and schedules. */
@MainThread
@TargetApi(Build.VERSION_CODES.N)
+@Singleton
public class DvrDataManagerImpl extends BaseDvrDataManager {
private static final String TAG = "DvrDataManagerImpl";
private static final boolean DEBUG = false;
@@ -98,6 +108,7 @@
private final HashMap<Long, SeriesRecording> mSeriesRecordingsForRemovedInput = new HashMap<>();
private final Context mContext;
+ private final DvrDatabaseHelper mDbHelper;
private Executor mDbExecutor;
private final ContentObserver mContentObserver =
new ContentObserver(new Handler(Looper.getMainLooper())) {
@@ -187,20 +198,28 @@
return moved;
}
- public DvrDataManagerImpl(Context context, Clock clock) {
+ @Inject
+ public DvrDataManagerImpl(
+ @ApplicationContext Context context,
+ Clock clock,
+ TvInputManagerHelper tvInputManagerHelper,
+ @DbExecutor Executor dbExecutor,
+ DvrDatabaseHelper dbHelper) {
super(context, clock);
mContext = context;
TvSingletons tvSingletons = TvSingletons.getSingletons(context);
- mInputManager = tvSingletons.getTvInputManagerHelper();
+ mInputManager = tvInputManagerHelper;
mStorageStatusManager = tvSingletons.getRecordingStorageStatusManager();
- mDbExecutor = tvSingletons.getDbExecutor();
+ mDbExecutor = dbExecutor;
+ mDbHelper = dbHelper;
+ start();
}
- public void start() {
+ private void start() {
mInputManager.addCallback(mInputCallback);
mStorageStatusManager.addListener(mStorageMountChangedListener);
DvrQuerySeriesRecordingFuture dvrQuerySeriesRecordingTask =
- new DvrQuerySeriesRecordingFuture(mContext);
+ new DvrQuerySeriesRecordingFuture(mDbHelper);
ListenableFuture<List<SeriesRecording>> dvrQuerySeriesRecordingFuture =
dvrQuerySeriesRecordingTask.executeOnDbThread(
new FutureCallback<List<SeriesRecording>>() {
@@ -213,7 +232,8 @@
if (SoftPreconditions.checkState(
!seriesIds.contains(r.getSeriesId()),
TAG,
- "Skip loading series recording with duplicate series ID: "
+ "Skip loading series recording with duplicate series"
+ + " ID: "
+ r)) {
seriesIds.add(r.getSeriesId());
if (isInputAvailable(r.getInputId())) {
@@ -237,7 +257,7 @@
}
});
mPendingDvrFuture.add(dvrQuerySeriesRecordingFuture);
- DvrQueryScheduleFuture dvrQueryScheduleTask = new DvrQueryScheduleFuture(mContext);
+ DvrQueryScheduleFuture dvrQueryScheduleTask = new DvrQueryScheduleFuture(mDbHelper);
ListenableFuture<List<ScheduledRecording>> dvrQueryScheduleFuture =
dvrQueryScheduleTask.executeOnDbThread(
new FutureCallback<List<ScheduledRecording>>() {
@@ -641,7 +661,7 @@
notifyScheduledRecordingAdded(schedules);
}
ListenableFuture addScheduleFuture =
- new AddScheduleFuture(mContext)
+ new AddScheduleFuture(mDbHelper)
.executeOnDbThread(removeFromSetOnCompletion, schedules);
mNoStopFuture.add(addScheduleFuture);
removeDeletedSchedules(schedules);
@@ -663,7 +683,7 @@
notifySeriesRecordingAdded(seriesRecordings);
}
ListenableFuture addSeriesRecordingFuture =
- new AddSeriesRecordingFuture(mContext)
+ new AddSeriesRecordingFuture(mDbHelper)
.executeOnDbThread(removeFromSetOnCompletion, seriesRecordings);
mNoStopFuture.add(addSeriesRecordingFuture);
}
@@ -723,7 +743,7 @@
}
if (!schedulesToDelete.isEmpty()) {
ListenableFuture deleteScheduleFuture =
- new DeleteScheduleFuture(mContext)
+ new DeleteScheduleFuture(mDbHelper)
.executeOnDbThread(
removeFromSetOnCompletion,
ScheduledRecording.toArray(schedulesToDelete));
@@ -731,7 +751,7 @@
}
if (!schedulesNotToDelete.isEmpty()) {
ListenableFuture updateScheduleFuture =
- new UpdateScheduleFuture(mContext)
+ new UpdateScheduleFuture(mDbHelper)
.executeOnDbThread(
removeFromSetOnCompletion,
ScheduledRecording.toArray(schedulesNotToDelete));
@@ -774,7 +794,7 @@
notifySeriesRecordingRemoved(seriesRecordings);
}
ListenableFuture deleteSeriesRecordingFuture =
- new DeleteSeriesRecordingFuture(mContext)
+ new DeleteSeriesRecordingFuture(mDbHelper)
.executeOnDbThread(removeFromSetOnCompletion, seriesRecordings);
mNoStopFuture.add(deleteSeriesRecordingFuture);
removeDeletedSchedules(seriesRecordings);
@@ -829,7 +849,7 @@
}
if (updateDb) {
ListenableFuture updateScheduleFuture =
- new UpdateScheduleFuture(mContext)
+ new UpdateScheduleFuture(mDbHelper)
.executeOnDbThread(removeFromSetOnCompletion, scheduleArray);
mNoStopFuture.add(updateScheduleFuture);
}
@@ -856,7 +876,7 @@
notifySeriesRecordingChanged(seriesRecordings);
}
ListenableFuture updateSeriesRecordingFuture =
- new UpdateSeriesRecordingFuture(mContext)
+ new UpdateSeriesRecordingFuture(mDbHelper)
.executeOnDbThread(removeFromSetOnCompletion, seriesRecordings);
mNoStopFuture.add(updateSeriesRecordingFuture);
}
@@ -877,7 +897,7 @@
}
if (!schedulesToDelete.isEmpty()) {
ListenableFuture deleteScheduleFuture =
- new DeleteScheduleFuture(mContext)
+ new DeleteScheduleFuture(mDbHelper)
.executeOnDbThread(
removeFromSetOnCompletion,
ScheduledRecording.toArray(schedulesToDelete));
@@ -902,7 +922,7 @@
}
if (!schedulesToDelete.isEmpty()) {
ListenableFuture deleteScheduleFuture =
- new DeleteScheduleFuture(mContext)
+ new DeleteScheduleFuture(mDbHelper)
.executeOnDbThread(
removeFromSetOnCompletion,
ScheduledRecording.toArray(schedulesToDelete));
@@ -950,7 +970,7 @@
mSeriesRecordingsForRemovedInput.remove(r.getId());
}
ListenableFuture deleteSeriesRecordingFuture =
- new DeleteSeriesRecordingFuture(mContext)
+ new DeleteSeriesRecordingFuture(mDbHelper)
.executeOnDbThread(
removeFromSetOnCompletion,
SeriesRecording.toArray(removedSeriesRecordings));
@@ -1043,13 +1063,13 @@
}
}
ListenableFuture deleteScheduleFuture =
- new DeleteScheduleFuture(mContext)
+ new DeleteScheduleFuture(mDbHelper)
.executeOnDbThread(
removeFromSetOnCompletion,
ScheduledRecording.toArray(schedulesToDelete));
mNoStopFuture.add(deleteScheduleFuture);
ListenableFuture deleteSeriesRecordingFuture =
- new DeleteSeriesRecordingFuture(mContext)
+ new DeleteSeriesRecordingFuture(mDbHelper)
.executeOnDbThread(
removeFromSetOnCompletion,
SeriesRecording.toArray(seriesRecordingsToDelete));
@@ -1085,7 +1105,7 @@
if (!removedSeriesRecordings.isEmpty()) {
SeriesRecording[] removed = SeriesRecording.toArray(removedSeriesRecordings);
ListenableFuture deleteSeriesRecordingFuture =
- new DeleteSeriesRecordingFuture(mContext)
+ new DeleteSeriesRecordingFuture(mDbHelper)
.executeOnDbThread(removeFromSetOnCompletion, removed);
mNoStopFuture.add(deleteSeriesRecordingFuture);
if (mDvrLoadFinished) {
diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java
index cc9ad37..12982c6 100644
--- a/src/com/android/tv/dvr/DvrManager.java
+++ b/src/com/android/tv/dvr/DvrManager.java
@@ -37,12 +37,13 @@
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.Range;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.util.CommonUtils;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
import com.android.tv.dvr.DvrScheduleManager.OnInitializeListener;
@@ -51,6 +52,7 @@
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.Utils;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -187,7 +189,7 @@
? mScheduleManager.suggestNewPriority()
: mScheduleManager.suggestHighestPriority(
seriesRecording.getInputId(),
- new Range(
+ Range.create(
program.getStartTimeUtcMillis(),
program.getEndTimeUtcMillis()),
seriesRecording.getPriority()));
diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java
index 7202dce..3afcc42 100644
--- a/src/com/android/tv/dvr/DvrScheduleManager.java
+++ b/src/com/android/tv/dvr/DvrScheduleManager.java
@@ -25,11 +25,12 @@
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.Range;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.data.ScheduledRecording;
@@ -37,6 +38,7 @@
import com.android.tv.dvr.recorder.InputTaskScheduler;
import com.android.tv.util.CompositeComparator;
import com.android.tv.util.Utils;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -76,7 +78,7 @@
ScheduledRecording.ID_COMPARATOR);
private final Context mContext;
- private final DvrDataManagerImpl mDataManager;
+ private final DvrDataManager mDataManager;
private final ChannelDataManager mChannelDataManager;
private final Map<String, List<ScheduledRecording>> mInputScheduleMap = new HashMap<>();
@@ -95,7 +97,7 @@
public DvrScheduleManager(Context context) {
mContext = context;
TvSingletons tvSingletons = TvSingletons.getSingletons(context);
- mDataManager = (DvrDataManagerImpl) tvSingletons.getDvrDataManager();
+ mDataManager = tvSingletons.getDvrDataManager();
mChannelDataManager = tvSingletons.getChannelDataManager();
if (mDataManager.isDvrScheduleLoadFinished() && mChannelDataManager.isDbLoadFinished()) {
buildData();
diff --git a/src/com/android/tv/dvr/DvrStorageStatusManager.java b/src/com/android/tv/dvr/DvrStorageStatusManager.java
index dc347a9..a0ae893 100644
--- a/src/com/android/tv/dvr/DvrStorageStatusManager.java
+++ b/src/com/android/tv/dvr/DvrStorageStatusManager.java
@@ -114,7 +114,7 @@
return;
}
for (TvInputInfo info : tvInputInfoList) {
- if (CommonUtils.isBundledInput(info.getId())) {
+ if (CommonUtils.isBundledInput(info.getId()) && dvrManager != null) {
dvrManager.forgetStorage(info.getId());
}
}
diff --git a/src/com/android/tv/dvr/DvrWatchedPositionManager.java b/src/com/android/tv/dvr/DvrWatchedPositionManager.java
index 8616962..b4fea7f 100644
--- a/src/com/android/tv/dvr/DvrWatchedPositionManager.java
+++ b/src/com/android/tv/dvr/DvrWatchedPositionManager.java
@@ -20,8 +20,10 @@
import android.content.SharedPreferences;
import android.media.tv.TvInputManager;
import android.support.annotation.IntDef;
+
import com.android.tv.common.util.SharedPreferencesUtils;
import com.android.tv.dvr.data.RecordedProgram;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -36,7 +38,7 @@
*/
public class DvrWatchedPositionManager {
private SharedPreferences mWatchedPositions;
- private final Map<Long, Set> mListeners = new HashMap<>();
+ private final Map<Long, Set<WatchedPositionChangedListener>> mListeners = new HashMap<>();
/**
* The minimum percentage of recorded program being watched that will be considered as being
diff --git a/src/com/android/tv/dvr/data/RecordedProgram.java b/src/com/android/tv/dvr/data/RecordedProgram.java
index 899e65a..6143055 100644
--- a/src/com/android/tv/dvr/data/RecordedProgram.java
+++ b/src/com/android/tv/dvr/data/RecordedProgram.java
@@ -36,9 +36,10 @@
import com.android.tv.common.data.RecordedProgramState;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.common.util.StringUtils;
-import com.android.tv.data.BaseProgram;
+import com.android.tv.data.BaseProgramImpl;
import com.android.tv.data.GenreItems;
import com.android.tv.data.InternalDataUtils;
+import com.android.tv.data.api.BaseProgram;
import com.android.tv.util.TvProviderUtils;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
@@ -49,7 +50,7 @@
/** Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}. */
@TargetApi(Build.VERSION_CODES.N)
@AutoValue
-public abstract class RecordedProgram extends BaseProgram {
+public abstract class RecordedProgram extends BaseProgramImpl {
public static final int ID_NOT_SET = -1;
private static final String TAG = "RecordedProgram";
diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java
index ba6d3cf..1237fb3 100644
--- a/src/com/android/tv/dvr/data/ScheduledRecording.java
+++ b/src/com/android/tv/dvr/data/ScheduledRecording.java
@@ -27,15 +27,17 @@
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Range;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.util.CommonUtils;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.util.CompositeComparator;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
@@ -492,7 +494,7 @@
}
};
- /** The ID internal to Live TV */
+ /** The ID internal to TV app */
private long mId;
/**
diff --git a/src/com/android/tv/dvr/data/SeriesRecording.java b/src/com/android/tv/dvr/data/SeriesRecording.java
index 6cb0e83..cd7d966 100644
--- a/src/com/android/tv/dvr/data/SeriesRecording.java
+++ b/src/com/android/tv/dvr/data/SeriesRecording.java
@@ -22,11 +22,14 @@
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.text.TextUtils;
-import com.android.tv.data.BaseProgram;
-import com.android.tv.data.Program;
+
+import com.android.tv.data.BaseProgramImpl;
+import com.android.tv.data.api.BaseProgram;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
import com.android.tv.util.Utils;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
@@ -85,7 +88,8 @@
(SeriesRecording lhs, SeriesRecording rhs) -> Long.compare(lhs.mId, rhs.mId);
/**
- * Creates a new Builder with the values set from the series information of {@link BaseProgram}.
+ * Creates a new Builder with the values set from the series information of {@link
+ * BaseProgramImpl}.
*/
public static Builder builder(String inputId, BaseProgram p) {
return new Builder()
diff --git a/src/com/android/tv/dvr/provider/DvrContract.java b/src/com/android/tv/dvr/provider/DvrContract.java
index a5f2e2c..8539ae3 100644
--- a/src/com/android/tv/dvr/provider/DvrContract.java
+++ b/src/com/android/tv/dvr/provider/DvrContract.java
@@ -20,7 +20,7 @@
/**
* The contract between the DVR provider and applications. Contains definitions for the supported
- * columns. It's for the internal use in Live TV.
+ * columns. It's for the internal use in TV app.
*/
public final class DvrContract {
/** Column definition for Schedules table. */
@@ -69,8 +69,8 @@
public static final String FAILED_REASON_INVALID_CHANNEL = "FAILED_REASON_INVALID_CHANNEL";
/** The recording failed because the scheduler was stopped */
- public static final String FAILED_REASON_SCHEDULER_STOPPED
- = "FAILED_REASON_SCHEDULER_STOPPED";
+ public static final String FAILED_REASON_SCHEDULER_STOPPED =
+ "FAILED_REASON_SCHEDULER_STOPPED";
/** The recording failed because some messages were not sent to the message queue */
public static final String FAILED_REASON_MESSAGE_NOT_SENT =
@@ -84,8 +84,7 @@
"FAILED_REASON_CONNECTION_FAILED";
/**
- * The recording failed because a required recording resource was not able to be
- * allocated.
+ * The recording failed because a required recording resource was not able to be allocated.
*/
public static final String FAILED_REASON_RESOURCE_BUSY = "FAILED_REASON_RESOURCE_BUSY";
diff --git a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
index ebf133d..1dcda8e 100644
--- a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
+++ b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
@@ -26,12 +26,18 @@
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
+
+import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/** A data class for one recorded contents. */
+@Singleton
public class DvrDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "DvrDatabaseHelper";
private static final boolean DEBUG = false;
@@ -238,8 +244,9 @@
return "DELETE FROM " + tableName + " WHERE " + BaseColumns._ID + "=?";
}
- public DvrDatabaseHelper(Context context) {
- super(context.getApplicationContext(), DB_NAME, null, DATABASE_VERSION);
+ @Inject
+ public DvrDatabaseHelper(@ApplicationContext Context context) {
+ super(context, DB_NAME, null, DATABASE_VERSION);
}
@Override
@@ -266,8 +273,12 @@
return;
}
if (oldVersion < 18) {
- db.execSQL("ALTER TABLE " + Schedules.TABLE_NAME + " ADD COLUMN "
- + Schedules.COLUMN_FAILED_REASON + " TEXT DEFAULT null;");
+ db.execSQL(
+ "ALTER TABLE "
+ + Schedules.TABLE_NAME
+ + " ADD COLUMN "
+ + Schedules.COLUMN_FAILED_REASON
+ + " TEXT DEFAULT null;");
}
}
diff --git a/src/com/android/tv/dvr/provider/DvrDbFuture.java b/src/com/android/tv/dvr/provider/DvrDbFuture.java
index ae8c480..cbc2c07 100644
--- a/src/com/android/tv/dvr/provider/DvrDbFuture.java
+++ b/src/com/android/tv/dvr/provider/DvrDbFuture.java
@@ -16,21 +16,23 @@
package com.android.tv.dvr.provider;
-import android.content.Context;
import android.database.Cursor;
import android.support.annotation.Nullable;
import android.util.Log;
+
import com.android.tv.common.concurrent.NamedThreadFactory;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
import com.android.tv.util.MainThreadExecutor;
+
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
@@ -38,29 +40,24 @@
/** {@link DvrDbFuture} that defaults to executing on its own single threaded Executor Service. */
public abstract class DvrDbFuture<ParamsT, ResultT> {
private static final NamedThreadFactory THREAD_FACTORY =
- new NamedThreadFactory(DvrDbFuture.class.getSimpleName());
+ new NamedThreadFactory(DvrDbFuture.class.getSimpleName());
private static final ListeningExecutorService DB_EXECUTOR =
- MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(THREAD_FACTORY));
+ MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(THREAD_FACTORY));
- private static DvrDatabaseHelper sDbHelper;
+ final DvrDatabaseHelper mDbHelper;
private ListenableFuture<ResultT> mFuture;
- final Context mContext;
-
- private DvrDbFuture(Context context) {
- mContext = context;
+ private DvrDbFuture(DvrDatabaseHelper mDbHelper) {
+ this.mDbHelper = mDbHelper;
}
- /** Execute the task on the {@link #DB_EXECUTOR} thread and return Future*/
+ /** Execute the task on the {@link #DB_EXECUTOR} thread and return Future */
@SafeVarargs
public final ListenableFuture<ResultT> executeOnDbThread(
- FutureCallback<ResultT> callback, ParamsT... params) {
- if (sDbHelper == null) {
- sDbHelper = new DvrDatabaseHelper(mContext.getApplicationContext());
- }
- mFuture = DB_EXECUTOR.submit(() -> dbHelperInBackground(params));
- Futures.addCallback(mFuture, callback, MainThreadExecutor.getInstance());
- return mFuture;
+ FutureCallback<ResultT> callback, ParamsT... params) {
+ mFuture = DB_EXECUTOR.submit(() -> dbHelperInBackground(params));
+ Futures.addCallback(mFuture, callback, MainThreadExecutor.getInstance());
+ return mFuture;
}
/** Executes in the background after initializing DbHelper} */
@@ -72,52 +69,48 @@
}
/** Inserts schedules. */
- public static class AddScheduleFuture
- extends DvrDbFuture<ScheduledRecording, Void> {
- public AddScheduleFuture(Context context) {
- super(context);
+ public static class AddScheduleFuture extends DvrDbFuture<ScheduledRecording, Void> {
+ public AddScheduleFuture(DvrDatabaseHelper dbHelper) {
+ super(dbHelper);
}
@Override
protected final Void dbHelperInBackground(ScheduledRecording... params) {
- sDbHelper.insertSchedules(params);
+ mDbHelper.insertSchedules(params);
return null;
}
}
/** Update schedules. */
- public static class UpdateScheduleFuture
- extends DvrDbFuture<ScheduledRecording, Void> {
- public UpdateScheduleFuture(Context context) {
- super(context);
+ public static class UpdateScheduleFuture extends DvrDbFuture<ScheduledRecording, Void> {
+ public UpdateScheduleFuture(DvrDatabaseHelper dbHelper) {
+ super(dbHelper);
}
@Override
protected final Void dbHelperInBackground(ScheduledRecording... params) {
- sDbHelper.updateSchedules(params);
+ mDbHelper.updateSchedules(params);
return null;
}
}
/** Delete schedules. */
- public static class DeleteScheduleFuture
- extends DvrDbFuture<ScheduledRecording, Void> {
- public DeleteScheduleFuture(Context context) {
- super(context);
+ public static class DeleteScheduleFuture extends DvrDbFuture<ScheduledRecording, Void> {
+ public DeleteScheduleFuture(DvrDatabaseHelper dbHelper) {
+ super(dbHelper);
}
@Override
protected final Void dbHelperInBackground(ScheduledRecording... params) {
- sDbHelper.deleteSchedules(params);
+ mDbHelper.deleteSchedules(params);
return null;
}
}
/** Returns all {@link ScheduledRecording}s. */
- public static class DvrQueryScheduleFuture
- extends DvrDbFuture<Void, List<ScheduledRecording>> {
- public DvrQueryScheduleFuture(Context context) {
- super(context);
+ public static class DvrQueryScheduleFuture extends DvrDbFuture<Void, List<ScheduledRecording>> {
+ public DvrQueryScheduleFuture(DvrDatabaseHelper dbHelper) {
+ super(dbHelper);
}
@Override
@@ -127,7 +120,7 @@
return null;
}
List<ScheduledRecording> scheduledRecordings = new ArrayList<>();
- try (Cursor c = sDbHelper.query(Schedules.TABLE_NAME, ScheduledRecording.PROJECTION)) {
+ try (Cursor c = mDbHelper.query(Schedules.TABLE_NAME, ScheduledRecording.PROJECTION)) {
while (c.moveToNext() && !isCancelled()) {
scheduledRecordings.add(ScheduledRecording.fromCursor(c));
}
@@ -137,43 +130,40 @@
}
/** Inserts series recordings. */
- public static class AddSeriesRecordingFuture
- extends DvrDbFuture<SeriesRecording, Void> {
- public AddSeriesRecordingFuture(Context context) {
- super(context);
+ public static class AddSeriesRecordingFuture extends DvrDbFuture<SeriesRecording, Void> {
+ public AddSeriesRecordingFuture(DvrDatabaseHelper dbHelper) {
+ super(dbHelper);
}
@Override
protected final Void dbHelperInBackground(SeriesRecording... params) {
- sDbHelper.insertSeriesRecordings(params);
+ mDbHelper.insertSeriesRecordings(params);
return null;
}
}
/** Update series recordings. */
- public static class UpdateSeriesRecordingFuture
- extends DvrDbFuture<SeriesRecording, Void> {
- public UpdateSeriesRecordingFuture(Context context) {
- super(context);
+ public static class UpdateSeriesRecordingFuture extends DvrDbFuture<SeriesRecording, Void> {
+ public UpdateSeriesRecordingFuture(DvrDatabaseHelper dbHelper) {
+ super(dbHelper);
}
@Override
protected final Void dbHelperInBackground(SeriesRecording... params) {
- sDbHelper.updateSeriesRecordings(params);
+ mDbHelper.updateSeriesRecordings(params);
return null;
}
}
/** Delete series recordings. */
- public static class DeleteSeriesRecordingFuture
- extends DvrDbFuture<SeriesRecording, Void> {
- public DeleteSeriesRecordingFuture(Context context) {
- super(context);
+ public static class DeleteSeriesRecordingFuture extends DvrDbFuture<SeriesRecording, Void> {
+ public DeleteSeriesRecordingFuture(DvrDatabaseHelper dbHelper) {
+ super(dbHelper);
}
@Override
protected final Void dbHelperInBackground(SeriesRecording... params) {
- sDbHelper.deleteSeriesRecordings(params);
+ mDbHelper.deleteSeriesRecordings(params);
return null;
}
}
@@ -183,8 +173,8 @@
extends DvrDbFuture<Void, List<SeriesRecording>> {
private static final String TAG = "DvrQuerySeriesRecording";
- public DvrQuerySeriesRecordingFuture(Context context) {
- super(context);
+ public DvrQuerySeriesRecordingFuture(DvrDatabaseHelper dbHelper) {
+ super(dbHelper);
}
@Override
@@ -195,7 +185,7 @@
}
List<SeriesRecording> scheduledRecordings = new ArrayList<>();
try (Cursor c =
- sDbHelper.query(SeriesRecordings.TABLE_NAME, SeriesRecording.PROJECTION)) {
+ mDbHelper.query(SeriesRecordings.TABLE_NAME, SeriesRecording.PROJECTION)) {
while (c.moveToNext() && !isCancelled()) {
scheduledRecordings.add(SeriesRecording.fromCursor(c));
}
diff --git a/src/com/android/tv/dvr/provider/DvrDbSync.java b/src/com/android/tv/dvr/provider/DvrDbSync.java
index 7658ca4..c2eae77 100644
--- a/src/com/android/tv/dvr/provider/DvrDbSync.java
+++ b/src/com/android/tv/dvr/provider/DvrDbSync.java
@@ -29,17 +29,19 @@
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
+
import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
-import com.android.tv.dvr.DvrDataManagerImpl;
import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.WritableDvrDataManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
import com.android.tv.util.AsyncDbTask.AsyncQueryProgramTask;
import com.android.tv.util.TvUriMatcher;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -49,6 +51,7 @@
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/**
* A class to synchronizes DVR DB with TvProvider.
@@ -65,9 +68,11 @@
private static final String TAG = "DvrDbSync";
private static final boolean DEBUG = false;
+ private static final long RECORD_MARGIN_MS = TimeUnit.SECONDS.toMillis(10);
+
private final Context mContext;
private final DvrManager mDvrManager;
- private final DvrDataManagerImpl mDataManager;
+ private final WritableDvrDataManager mDataManager;
private final ChannelDataManager mChannelDataManager;
private final Executor mDbExecutor;
private final Queue<Long> mProgramIdQueue = new LinkedList<>();
@@ -138,7 +143,7 @@
}
};
- public DvrDbSync(Context context, DvrDataManagerImpl dataManager) {
+ public DvrDbSync(Context context, WritableDvrDataManager dataManager) {
this(
context,
dataManager,
@@ -151,7 +156,7 @@
@VisibleForTesting
DvrDbSync(
Context context,
- DvrDataManagerImpl dataManager,
+ WritableDvrDataManager dataManager,
ChannelDataManager channelDataManager,
DvrManager dvrManager,
SeriesRecordingScheduler seriesRecordingScheduler,
@@ -325,10 +330,15 @@
// Old program belongs to a series but the new one doesn't.
seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule);
}
- // Change start time only when the recording is not started yet.
+ // Change start time only when the recording is not started yet and if it is not
+ // within marginal time of current time. Marginal check is needed to prevent the
+ // update of start time if recording is just triggered or about to get triggered.
+ boolean marginalToCurrentTime = RECORD_MARGIN_MS >
+ Math.abs(System.currentTimeMillis() - schedule.getStartTimeMs());
boolean needToChangeStartTime =
schedule.getState() != ScheduledRecording.STATE_RECORDING_IN_PROGRESS
- && program.getStartTimeUtcMillis() != schedule.getStartTimeMs();
+ && program.getStartTimeUtcMillis() != schedule.getStartTimeMs()
+ && !marginalToCurrentTime;
if (needToChangeStartTime) {
builder.setStartTimeMs(program.getStartTimeUtcMillis());
needUpdate = true;
diff --git a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
index 02e197f..a66e5b0 100644
--- a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
+++ b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
@@ -25,16 +25,19 @@
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.util.PermissionUtils;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeasonEpisodeNumber;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.util.AsyncDbTask.AsyncProgramQueryTask;
import com.android.tv.util.AsyncDbTask.CursorFilter;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -47,11 +50,11 @@
public abstract class EpisodicProgramLoadTask {
private static final String TAG = "EpisodicProgramLoadTask";
- private static final int PROGRAM_ID_INDEX = Program.getColumnIndex(Programs._ID);
+ private static final int PROGRAM_ID_INDEX = ProgramImpl.getColumnIndex(Programs._ID);
private static final int START_TIME_INDEX =
- Program.getColumnIndex(Programs.COLUMN_START_TIME_UTC_MILLIS);
+ ProgramImpl.getColumnIndex(Programs.COLUMN_START_TIME_UTC_MILLIS);
private static final int RECORDING_PROHIBITED_INDEX =
- Program.getColumnIndex(Programs.COLUMN_RECORDING_PROHIBITED);
+ ProgramImpl.getColumnIndex(Programs.COLUMN_RECORDING_PROHIBITED);
private static final String PARAM_START_TIME = "start_time";
private static final String PARAM_END_TIME = "end_time";
@@ -289,7 +292,7 @@
&& mDisallowedProgramIds.contains(c.getLong(PROGRAM_ID_INDEX))) {
return false;
}
- Program program = Program.fromCursor(c);
+ Program program = ProgramImpl.fromCursor(c);
for (SeriesRecording seriesRecording : mSeriesRecordings) {
boolean programMatches;
if (mIgnoreChannelOption) {
diff --git a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
index 696038c..92b4e06 100644
--- a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
+++ b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
@@ -27,11 +27,12 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseArray;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.util.CollectionUtils;
import com.android.tv.common.util.SharedPreferencesUtils;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.Program;
import com.android.tv.data.epg.EpgReader;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
@@ -43,6 +44,9 @@
import com.android.tv.dvr.data.SeriesInfo;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
+
+import dagger.Lazy;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -54,7 +58,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
-import javax.inject.Provider;
/**
* Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for the {@link
@@ -529,10 +532,9 @@
private class FetchSeriesInfoTask extends AsyncTask<Void, Void, SeriesInfo> {
private final SeriesRecording mSeriesRecording;
- private final Provider<EpgReader> mEpgReaderProvider;
+ private final Lazy<EpgReader> mEpgReaderProvider;
- FetchSeriesInfoTask(
- SeriesRecording seriesRecording, Provider<EpgReader> epgReaderProvider) {
+ FetchSeriesInfoTask(SeriesRecording seriesRecording, Lazy<EpgReader> epgReaderProvider) {
mSeriesRecording = seriesRecording;
mEpgReaderProvider = epgReaderProvider;
}
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
index 5e3caa9..1f4faf3 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
@@ -22,13 +22,16 @@
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.RecordedProgram;
+
import java.util.List;
/**
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
index a6bbe13..56ffc88 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
@@ -22,14 +22,17 @@
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
import android.text.format.DateUtils;
+
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
+
import java.util.List;
/**
diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
index 6be35cb..b24281a 100644
--- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
@@ -18,9 +18,9 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
index 649cc89..5e0a96b 100644
--- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
@@ -21,22 +21,25 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.recorder.ConflictChecker;
import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener;
import com.android.tv.util.Utils;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
diff --git a/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java b/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java
index 611962d..81fc3ed 100644
--- a/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java
+++ b/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java
@@ -17,8 +17,8 @@
package com.android.tv.dvr.ui;
import android.content.Context;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidedActionsStylist;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
index a900cc7..fda4cde 100644
--- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
@@ -20,9 +20,9 @@
import android.app.DialogFragment;
import android.content.Context;
import android.os.Bundle;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.VerticalGridView;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidedAction;
+import androidx.leanback.widget.VerticalGridView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
index e6b54f6..e8f501e 100644
--- a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
@@ -20,13 +20,15 @@
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.leanback.app.GuidedStepFragment;
+
import com.android.tv.MainActivity;
import com.android.tv.R;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelWatchConflictFragment;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
@@ -36,7 +38,7 @@
public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
/** Key for input ID. Type: String. */
public static final String KEY_INPUT_ID = "DvrHalfSizedDialogFragment.input_id";
- /** Key for the program. Type: {@link com.android.tv.data.Program}. */
+ /** Key for the program. Type: {@link ProgramImpl}. */
public static final String KEY_PROGRAM = "DvrHalfSizedDialogFragment.program";
/** Key for the channel ID. Type: long. */
public static final String KEY_CHANNEL_ID = "DvrHalfSizedDialogFragment.channel_id";
diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
index 6fba4d9..01631b9 100644
--- a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
@@ -20,8 +20,8 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
diff --git a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
index 02b2da1..3237acd 100644
--- a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
@@ -21,8 +21,8 @@
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import android.util.Log;
import com.android.tv.R;
import com.android.tv.ui.DetailsActivity;
diff --git a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
index 5bb97e9..ae41f50 100644
--- a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
@@ -22,9 +22,9 @@
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidedActionsStylist;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
diff --git a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
index 72603d0..7131f62 100644
--- a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
@@ -22,18 +22,21 @@
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
import android.text.format.DateUtils;
+
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
+
import java.util.Collections;
import java.util.List;
@@ -52,7 +55,7 @@
private static final int ACTION_RECORD_EPISODE = 1;
private static final int ACTION_RECORD_SERIES = 2;
- private Program mProgram;
+ private ProgramImpl mProgram;
private boolean mAddCurrentProgramToSeries;
@Override
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
index a237f1d..730237c 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
@@ -20,15 +20,13 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
+import androidx.leanback.app.GuidedStepFragment;
import android.util.Log;
import android.widget.Toast;
-
import com.android.tv.R;
import com.android.tv.Starter;
import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrManager;
-
import java.util.ArrayList;
import java.util.List;
@@ -70,7 +68,7 @@
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
deleteSelectedIds(true);
} else {
- // NOTE: If Live TV ever supports both embedded and separate DVR inputs
+ // NOTE: If TV app ever supports both embedded and separate DVR inputs
// then we should try to do the delete regardless.
Log.i(
TAG,
@@ -93,14 +91,14 @@
dvrManager.removeRecordedPrograms(mIdsToDelete, deleteFiles);
}
Toast.makeText(
- this,
- getResources()
- .getQuantityString(
- R.plurals.dvr_msg_episodes_deleted,
- mIdsToDelete.size(),
- mIdsToDelete.size(),
- recordingSize),
- Toast.LENGTH_LONG)
+ this,
+ getResources()
+ .getQuantityString(
+ R.plurals.dvr_msg_episodes_deleted,
+ mIdsToDelete.size(),
+ mIdsToDelete.size(),
+ recordingSize),
+ Toast.LENGTH_LONG)
.show();
finish();
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
index ff21323..10ef226 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
@@ -19,10 +19,10 @@
import android.content.Context;
import android.media.tv.TvInputManager;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidedActionsStylist;
import android.text.TextUtils;
import android.view.ViewGroup.LayoutParams;
import android.widget.Toast;
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java
index 9acb5b5..d72099b 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java
@@ -18,7 +18,7 @@
import android.app.Activity;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
+import androidx.leanback.app.GuidedStepFragment;
import com.android.tv.R;
public class DvrSeriesScheduledDialogActivity extends Activity {
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
index c6e2685..7d36990 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
@@ -20,22 +20,26 @@
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
+
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidedAction;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
+
import java.util.List;
public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
/**
* The key for program list which will be passed to {@link DvrSeriesSchedulesFragment}. Type:
- * List<{@link Program}>
+ * List<{@link ProgramImpl}>
*/
public static final String SERIES_SCHEDULED_KEY_PROGRAMS = "series_scheduled_key_programs";
@@ -46,6 +50,7 @@
private SeriesRecording mSeriesRecording;
private boolean mShowViewScheduleOption;
private List<Program> mPrograms;
+ private String mSeriesRecordingTitle;
private int mSchedulesAddedCount = 0;
private boolean mHasConflict = false;
@@ -75,6 +80,7 @@
getActivity().finish();
return;
}
+ mSeriesRecordingTitle = mSeriesRecording.getTitle();
mPrograms = (List<Program>) BigArguments.getArgument(SERIES_SCHEDULED_KEY_PROGRAMS);
BigArguments.reset();
mSchedulesAddedCount =
@@ -162,7 +168,7 @@
R.plurals.dvr_series_scheduled_no_conflict,
mSchedulesAddedCount,
mSchedulesAddedCount,
- mSeriesRecording.getTitle());
+ mSeriesRecordingTitle);
} else {
// mInThisSeriesConflictCount equals 0 and mOutThisSeriesConflictCount equals 0 means
// mHasConflict is false. So we don't need to check that case.
@@ -172,7 +178,7 @@
R.plurals.dvr_series_scheduled_this_and_other_series_conflict,
mSchedulesAddedCount,
mSchedulesAddedCount,
- mSeriesRecording.getTitle(),
+ mSeriesRecordingTitle,
mInThisSeriesConflictCount + mOutThisSeriesConflictCount);
} else if (mInThisSeriesConflictCount != 0) {
return getResources()
@@ -180,7 +186,7 @@
R.plurals.dvr_series_recording_scheduled_only_this_series_conflict,
mSchedulesAddedCount,
mSchedulesAddedCount,
- mSeriesRecording.getTitle(),
+ mSeriesRecordingTitle,
mInThisSeriesConflictCount);
} else {
if (mOutThisSeriesConflictCount == 1) {
@@ -189,14 +195,14 @@
R.plurals.dvr_series_scheduled_only_other_series_one_conflict,
mSchedulesAddedCount,
mSchedulesAddedCount,
- mSeriesRecording.getTitle());
+ mSeriesRecordingTitle);
} else {
return getResources()
.getQuantityString(
R.plurals.dvr_series_scheduled_only_other_series_many_conflicts,
mSchedulesAddedCount,
mSchedulesAddedCount,
- mSeriesRecording.getTitle(),
+ mSeriesRecordingTitle,
mOutThisSeriesConflictCount);
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
index 1a51cf4..eb3ca28 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
@@ -19,10 +19,13 @@
import android.app.Activity;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
+
+import androidx.leanback.app.GuidedStepFragment;
+
import com.android.tv.R;
import com.android.tv.Starter;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.data.ProgramImpl;
/** Activity to show details view in DVR. */
public class DvrSeriesSettingsActivity extends Activity {
@@ -40,7 +43,7 @@
public static final String IS_WINDOW_TRANSLUCENT = "windows_translucent";
/**
* Name of the program list. The list contains the programs which belong to the series. Type:
- * List<{@link com.android.tv.data.Program}>
+ * List<{@link ProgramImpl}>
*/
public static final String PROGRAM_LIST = "program_list";
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
index eadb3b9..7fc201c 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
@@ -21,17 +21,19 @@
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
import android.util.LongSparseArray;
+
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidedActionsStylist;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelImpl;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
@@ -39,6 +41,7 @@
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.data.SeriesRecording.ChannelOption;
import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -69,6 +72,7 @@
private Program mCurrentProgram;
private String mFragmentTitle;
+ private String mSeriesRecordingTitle;
private String mProrityActionTitle;
private String mProrityActionHighestText;
private String mProrityActionLowestText;
@@ -92,6 +96,7 @@
getActivity().finish();
return;
}
+ mSeriesRecordingTitle = mSeriesRecording.getTitle();
mShowViewScheduleOptionInDialog =
getArguments()
.getBoolean(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG);
@@ -161,9 +166,8 @@
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String breadcrumb = mSeriesRecording.getTitle();
String title = mFragmentTitle;
- return new Guidance(title, null, breadcrumb, null);
+ return new Guidance(title, null, mSeriesRecordingTitle, null);
}
@Override
diff --git a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
index 1ab4c50..1475a8c 100644
--- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
@@ -23,13 +23,17 @@
import android.os.Bundle;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.data.ScheduledRecording;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -44,7 +48,7 @@
public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
/** The action ID for the stop action. */
public static final int ACTION_STOP = 1;
- /** Key for the program. Type: {@link com.android.tv.data.Program}. */
+ /** Key for the program. Type: {@link ProgramImpl}. */
public static final String KEY_REASON = "DvrStopRecordingFragment.type";
@Retention(RetentionPolicy.SOURCE)
diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java
index 15abf90..4a8ce04 100644
--- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java
@@ -18,7 +18,7 @@
import android.app.DialogFragment;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
+import androidx.leanback.app.GuidedStepFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
index 99211fd..0b8f5df 100644
--- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
@@ -20,8 +20,8 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidedAction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/src/com/android/tv/dvr/ui/DvrUiHelper.java b/src/com/android/tv/dvr/ui/DvrUiHelper.java
index a121cf9..657abfa 100644
--- a/src/com/android/tv/dvr/ui/DvrUiHelper.java
+++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java
@@ -33,6 +33,7 @@
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.widget.ImageView;
@@ -44,9 +45,9 @@
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.util.CommonUtils;
-import com.android.tv.data.BaseProgram;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.BaseProgram;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.RecordedProgram;
@@ -75,6 +76,7 @@
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -122,7 +124,7 @@
return;
}
Bundle args = new Bundle();
- args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program.toParcelable());
args.putBoolean(
DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES, addCurrentProgramToSeries);
showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true);
@@ -144,7 +146,7 @@
return;
}
Bundle args = new Bundle();
- args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program.toParcelable());
showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true);
}
@@ -227,7 +229,7 @@
return;
}
Bundle args = new Bundle();
- args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program.toParcelable());
showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true);
}
@@ -237,14 +239,18 @@
return;
}
Bundle args = new Bundle();
- args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program.toParcelable());
showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true);
}
/** Shows program information dialog. */
public static void showWriteStoragePermissionRationaleDialog(Activity activity) {
- showDialogFragment(activity, new DvrWriteStoragePermissionRationaleDialogFragment(),
- new Bundle(), false, false);
+ showDialogFragment(
+ activity,
+ new DvrWriteStoragePermissionRationaleDialogFragment(),
+ new Bundle(),
+ false,
+ false);
}
/**
@@ -459,7 +465,7 @@
boolean removeEmptySeriesSchedule,
boolean isWindowTranslucent,
boolean showViewScheduleOptionInDialog,
- Program currentProgram) {
+ @Nullable Program currentProgram) {
SeriesRecording series =
TvSingletons.getSingletons(context)
.getDvrDataManager()
@@ -481,13 +487,15 @@
new EpisodicProgramLoadTask(context, series) {
@Override
protected void onPostExecute(List<Program> loadedPrograms) {
- sProgressDialog.dismiss();
- sProgressDialog = null;
+ if (sProgressDialog != null) {
+ sProgressDialog.dismiss();
+ sProgressDialog = null;
+ }
startSeriesSettingsActivityInternal(
context,
seriesRecordingId,
loadedPrograms == null
- ? Collections.EMPTY_LIST
+ ? ImmutableList.of()
: loadedPrograms,
removeEmptySeriesSchedule,
isWindowTranslucent,
@@ -524,7 +532,7 @@
boolean removeEmptySeriesSchedule,
boolean isWindowTranslucent,
boolean showViewScheduleOptionInDialog,
- Program currentProgram) {
+ @Nullable Program currentProgram) {
SoftPreconditions.checkState(
programs != null, TAG, "Start series settings activity but programs is null");
Intent intent = new Intent(context, DvrSeriesSettingsActivity.class);
@@ -537,7 +545,9 @@
intent.putExtra(
DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG,
showViewScheduleOptionInDialog);
- intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram);
+ if (currentProgram != null) {
+ intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram.toParcelable());
+ }
context.startActivity(intent);
}
@@ -682,16 +692,18 @@
}
SpannableStringBuilder builder;
if (TextUtils.isEmpty(seasonNumber) || seasonNumber.equals("0")) {
- builder =
+ Spanned temp =
TextUtils.isEmpty(episodeNumber)
- ? new SpannableStringBuilder(title)
- : new SpannableStringBuilder(Html.fromHtml(context.getString(
- R.string.program_title_with_episode_number_no_season,
- title,
- episodeNumber)));
+ ? SpannableStringBuilder.valueOf(title)
+ : Html.fromHtml(
+ context.getString(
+ R.string.program_title_with_episode_number_no_season,
+ title,
+ episodeNumber));
+ builder = SpannableStringBuilder.valueOf(temp);
} else {
builder =
- new SpannableStringBuilder(
+ SpannableStringBuilder.valueOf(
Html.fromHtml(
context.getString(
R.string.program_title_with_episode_number,
diff --git a/src/com/android/tv/dvr/ui/DvrWriteStoragePermissionRationaleFragment.java b/src/com/android/tv/dvr/ui/DvrWriteStoragePermissionRationaleFragment.java
index c93f583..25f7f38 100644
--- a/src/com/android/tv/dvr/ui/DvrWriteStoragePermissionRationaleFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrWriteStoragePermissionRationaleFragment.java
@@ -19,8 +19,8 @@
import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidanceStylist;
+import androidx.leanback.widget.GuidedAction;
import com.android.tv.R;
diff --git a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
index 1eb8080..7a26d5e 100644
--- a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
+++ b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
@@ -17,8 +17,8 @@
package com.android.tv.dvr.ui;
import android.support.annotation.VisibleForTesting;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.PresenterSelector;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.PresenterSelector;
import com.android.tv.common.SoftPreconditions;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java
index 0172f76..c0a57c0 100644
--- a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java
+++ b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java
@@ -17,8 +17,8 @@
package com.android.tv.dvr.ui;
import android.content.Context;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidedAction;
import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
diff --git a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
index 41ace9a..a06705c 100644
--- a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
+++ b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
@@ -17,10 +17,10 @@
package com.android.tv.dvr.ui.browse;
import android.graphics.drawable.Drawable;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
+import androidx.leanback.R;
+import androidx.leanback.widget.Action;
+import androidx.leanback.widget.Presenter;
+import androidx.leanback.widget.PresenterSelector;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
index 8c311d6..6d2402f 100644
--- a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
@@ -19,12 +19,13 @@
import android.content.Context;
import android.content.res.Resources;
import android.media.tv.TvInputManager;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+
+import androidx.leanback.widget.Action;
+import androidx.leanback.widget.OnActionClickedListener;
+import androidx.leanback.widget.SparseArrayObjectAdapter;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
-import com.android.tv.common.flags.has.HasConcurrentDvrPlaybackFlags;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
@@ -33,8 +34,13 @@
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
import com.android.tv.dvr.ui.DvrUiHelper;
+
+import dagger.android.AndroidInjection;
+
import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import javax.inject.Inject;
+
/** {@link RecordingDetailsFragment} for current recording in DVR. */
public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
private static final int ACTION_STOP_RECORDING = 1;
@@ -43,8 +49,8 @@
private DvrDataManager mDvrDataManger;
private RecordedProgram mRecordedProgram;
- private DvrWatchedPositionManager mDvrWatchedPositionManager;
- private ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
+ @Inject DvrWatchedPositionManager mDvrWatchedPositionManager;
+ @Inject ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
private boolean mPaused;
private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener =
new DvrDataManager.ScheduledRecordingListener() {
@@ -76,12 +82,10 @@
@Override
public void onAttach(Context context) {
+ AndroidInjection.inject(this);
super.onAttach(context);
mDvrDataManger = TvSingletons.getSingletons(context).getDvrDataManager();
mDvrDataManger.addScheduledRecordingListener(mScheduledRecordingListener);
- mDvrWatchedPositionManager =
- TvSingletons.getSingletons(getActivity()).getDvrWatchedPositionManager();
- mConcurrentDvrPlaybackFlags = HasConcurrentDvrPlaybackFlags.fromContext(context);
}
@Override
@@ -115,9 +119,7 @@
res.getString(R.string.dvr_detail_stop_recording),
null,
res.getDrawable(R.drawable.lb_ic_stop)));
- if (mConcurrentDvrPlaybackFlags.enabled()
- && mRecordedProgram != null
- && mRecordedProgram.isPartial()) {
+ if (mRecordedProgram != null && mRecordedProgram.isPartial()) {
if (mDvrWatchedPositionManager.getWatchedStatus(mRecordedProgram)
== DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) {
adapter.set(
diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
index e179743..b5e0526 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsContent.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
@@ -20,10 +20,11 @@
import android.media.tv.TvContract;
import android.support.annotation.Nullable;
import android.text.TextUtils;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
@@ -126,9 +127,10 @@
}
private static String getErrorMessage(Context context, ScheduledRecording recording) {
- int reason = recording.getFailedReason() == null
- ? ScheduledRecording.FAILED_REASON_OTHER
- : recording.getFailedReason();
+ int reason =
+ recording.getFailedReason() == null
+ ? ScheduledRecording.FAILED_REASON_OTHER
+ : recording.getFailedReason();
switch (reason) {
case ScheduledRecording.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED:
return context.getString(R.string.dvr_recording_failed_not_started);
@@ -136,8 +138,7 @@
return context.getString(R.string.dvr_recording_failed_resource_busy);
case ScheduledRecording.FAILED_REASON_INPUT_UNAVAILABLE:
return context.getString(
- R.string.dvr_recording_failed_input_unavailable,
- recording.getInputId());
+ R.string.dvr_recording_failed_input_unavailable, recording.getInputId());
case ScheduledRecording.FAILED_REASON_INPUT_DVR_UNSUPPORTED:
return context.getString(R.string.dvr_recording_failed_input_dvr_unsupported);
case ScheduledRecording.FAILED_REASON_INSUFFICIENT_SPACE:
diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
index 6b5fd1f..fafc70c 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
@@ -24,7 +24,7 @@
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
-import android.support.v17.leanback.widget.Presenter;
+import androidx.leanback.widget.Presenter;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -40,10 +40,10 @@
/**
* An {@link Presenter} for rendering a detailed description of an DVR item. Typically this
* Presenter will be used in a {@link
- * android.support.v17.leanback.widget.DetailsOverviewRowPresenter}. Most codes of this class is
- * originated from {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}.
+ * androidx.leanback.widget.DetailsOverviewRowPresenter}. Most codes of this class is
+ * originated from {@link androidx.leanback.widget.AbstractDetailsDescriptionPresenter}.
* The latter class are re-used to provide a customized version of {@link
- * android.support.v17.leanback.widget.DetailsOverviewRow}.
+ * androidx.leanback.widget.DetailsOverviewRow}.
*/
public class DetailsContentPresenter extends Presenter {
/** The ViewHolder for the {@link DetailsContentPresenter}. */
diff --git a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
index 4e41dae..8a4c785 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
@@ -21,7 +21,7 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.support.v17.leanback.app.BackgroundManager;
+import androidx.leanback.app.BackgroundManager;
/** The Background Helper. */
public class DetailsViewBackgroundHelper {
diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
index 5743ea5..7262b4a 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
@@ -22,13 +22,13 @@
import android.os.Bundle;
import com.android.tv.R;
import com.android.tv.Starter;
-import com.android.tv.perf.PerformanceMonitorManagerFactory;
+import com.android.tv.perf.StartupMeasureFactory;
/** {@link android.app.Activity} for DVR UI. */
public class DvrBrowseActivity extends Activity {
{
- PerformanceMonitorManagerFactory.create().getStartupMeasure().onActivityInit();
+ StartupMeasureFactory.create().onActivityInit();
}
private DvrBrowseFragment mFragment;
diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
index 17ba193..786942e 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
@@ -21,13 +21,14 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.support.v17.leanback.app.BrowseFragment;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.text.TextUtils;
+import androidx.leanback.app.BrowseFragment;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.HeaderItem;
+import androidx.leanback.widget.ListRow;
+import androidx.leanback.widget.Presenter;
+import androidx.leanback.widget.TitleViewAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
@@ -475,7 +476,7 @@
mRecentAdapter.add(recordedProgram);
String seriesId = recordedProgram.getSeriesId();
SeriesRecording seriesRecording = null;
- if (seriesId != null) {
+ if (!TextUtils.isEmpty(seriesId)) {
seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId);
if (latestProgram == null
@@ -499,7 +500,7 @@
private void handleRecordedProgramRemoved(RecordedProgram recordedProgram) {
mRecentAdapter.remove(recordedProgram);
String seriesId = recordedProgram.getSeriesId();
- if (seriesId != null) {
+ if (!TextUtils.isEmpty(seriesId)) {
SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
RecordedProgram latestProgram =
mSeriesId2LatestProgram.get(recordedProgram.getSeriesId());
@@ -520,7 +521,7 @@
mRecentAdapter.change(recordedProgram);
String seriesId = recordedProgram.getSeriesId();
SeriesRecording seriesRecording = null;
- if (seriesId != null) {
+ if (!TextUtils.isEmpty(seriesId)) {
seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId);
if (latestProgram == null
@@ -663,7 +664,7 @@
}
}
if (getSelectedPosition() >= mRowsAdapter.size()) {
- setSelectedPosition(mRecentAdapter.size() - 1);
+ setSelectedPosition(mRowsAdapter.size() - 1);
}
}
diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
index f90981f..a38180a 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
@@ -25,15 +25,15 @@
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
-import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.support.v17.leanback.widget.VerticalGridView;
+import androidx.leanback.app.DetailsFragment;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.DetailsOverviewRow;
+import androidx.leanback.widget.DetailsOverviewRowPresenter;
+import androidx.leanback.widget.OnActionClickedListener;
+import androidx.leanback.widget.PresenterSelector;
+import androidx.leanback.widget.SparseArrayObjectAdapter;
+import androidx.leanback.widget.VerticalGridView;
import android.text.TextUtils;
import android.widget.Toast;
import com.android.tv.R;
diff --git a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
index 4298d86..ebdee32 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
@@ -19,7 +19,7 @@
import android.app.Activity;
import android.content.Context;
import android.support.annotation.CallSuper;
-import android.support.v17.leanback.widget.Presenter;
+import androidx.leanback.widget.Presenter;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
diff --git a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
index a2d1cb2..625f8f7 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
@@ -17,7 +17,7 @@
package com.android.tv.dvr.ui.browse;
import android.content.Context;
-import android.support.v17.leanback.widget.ListRowPresenter;
+import androidx.leanback.widget.ListRowPresenter;
import android.view.ViewGroup;
import com.android.tv.R;
diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
index bf96354..5f58af8 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
@@ -19,9 +19,9 @@
import android.content.res.Resources;
import android.media.tv.TvInputManager;
import android.os.Bundle;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import androidx.leanback.widget.Action;
+import androidx.leanback.widget.OnActionClickedListener;
+import androidx.leanback.widget.SparseArrayObjectAdapter;
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.util.PermissionUtils;
@@ -32,7 +32,7 @@
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.ui.DetailsActivity;
-/** {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR. */
+/** {@link androidx.leanback.app.DetailsFragment} for recorded program in DVR. */
public class RecordedProgramDetailsFragment extends DvrDetailsFragment
implements DvrDataManager.RecordedProgramListener {
private static final int ACTION_RESUME_PLAYING = 1;
diff --git a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
index c83ceaf..ac7c574 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
@@ -23,14 +23,16 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.BaseCardView;
+import android.text.Layout;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.leanback.widget.BaseCardView;
import com.android.tv.R;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.ui.ViewUtils;
@@ -42,7 +44,7 @@
*/
public class RecordingCardView extends BaseCardView {
// This value should be the same with
- // android.support.v17.leanback.widget.FocusHighlightHelper.BrowseItemFocusHighlight.DURATION_MS
+ // androidx.leanback.widget.FocusHighlightHelper.BrowseItemFocusHighlight.DURATION_MS
private static final int ANIMATION_DURATION = 150;
private final ImageView mImageView;
private final int mImageWidth;
@@ -62,6 +64,7 @@
private final boolean mExpandTitleWhenFocused;
private boolean mExpanded;
private String mDetailBackgroundImageUri;
+ private Layout mTitleViewLayout;
public RecordingCardView(Context context) {
this(context, false);
@@ -120,6 +123,14 @@
* value));
}
});
+ getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mTitleViewLayout = mFoldedTitleView.getLayout();
+ }
+ });
mExpandTitleWhenFocused = expandTitleWhenFocused;
}
@@ -154,7 +165,8 @@
* @param withAnimation {@code true} to expand/fold with animation.
*/
public void expandTitle(boolean expand, boolean withAnimation) {
- if (expand != mExpanded && mFoldedTitleView.getLayout().getEllipsisCount(0) > 0) {
+ if (expand != mExpanded && mTitleViewLayout != null
+ && mTitleViewLayout.getEllipsisCount(0) > 0) {
if (withAnimation) {
if (expand) {
mExpandTitleAnimator.start();
diff --git a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
index 243681c..e85f983 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
@@ -17,7 +17,7 @@
package com.android.tv.dvr.ui.browse;
import android.os.Bundle;
-import android.support.v17.leanback.app.DetailsFragment;
+import androidx.leanback.app.DetailsFragment;
import com.android.tv.TvSingletons;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.ui.DetailsActivity;
diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
index f08bb12..7ef8e59 100644
--- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
@@ -18,9 +18,9 @@
import android.content.res.Resources;
import android.os.Bundle;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import androidx.leanback.widget.Action;
+import androidx.leanback.widget.OnActionClickedListener;
+import androidx.leanback.widget.SparseArrayObjectAdapter;
import com.android.tv.R;
import com.android.tv.TvSingletons;
diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
index 9104ef1..1c02009 100644
--- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
@@ -21,21 +21,21 @@
import android.media.tv.TvInputManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
-import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.text.TextUtils;
+import androidx.leanback.app.DetailsFragment;
+import androidx.leanback.widget.Action;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.DetailsOverviewRow;
+import androidx.leanback.widget.DetailsOverviewRowPresenter;
+import androidx.leanback.widget.HeaderItem;
+import androidx.leanback.widget.ListRow;
+import androidx.leanback.widget.OnActionClickedListener;
+import androidx.leanback.widget.PresenterSelector;
+import androidx.leanback.widget.SparseArrayObjectAdapter;
import com.android.tv.R;
import com.android.tv.TvSingletons;
-import com.android.tv.data.BaseProgram;
+import com.android.tv.data.api.BaseProgram;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.data.RecordedProgram;
diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
index 77a6350..293e1d9 100644
--- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
@@ -17,8 +17,8 @@
package com.android.tv.dvr.ui.list;
import android.os.Bundle;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.app.DetailsFragment;
+import androidx.leanback.widget.ClassPresenterSelector;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java b/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java
index 0ca05fa..1d93c8c 100644
--- a/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java
@@ -17,23 +17,24 @@
package com.android.tv.dvr.ui.list;
import android.os.Bundle;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.leanback.app.DetailsFragment;
+import androidx.leanback.widget.ClassPresenterSelector;
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter;
+import com.android.tv.common.flags.UiFlags;
/** A fragment to show the DVR history. */
public class DvrHistoryFragment extends DetailsFragment
implements DvrDataManager.ScheduledRecordingListener,
- DvrDataManager.RecordedProgramListener {
+ DvrDataManager.RecordedProgramListener {
private DvrHistoryRowAdapter mRowsAdapter;
private TextView mEmptyInfoScreenView;
@@ -48,11 +49,17 @@
presenterSelector.addClassPresenter(
ScheduleRow.class, new ScheduleRowPresenter(getContext()));
TvSingletons singletons = TvSingletons.getSingletons(getContext());
- mRowsAdapter = new DvrHistoryRowAdapter(
- getContext(), presenterSelector, singletons.getClock());
+ UiFlags uiFlags = singletons.getUiFlags();
+ mDvrDataManager = singletons.getDvrDataManager();
+ mRowsAdapter =
+ new DvrHistoryRowAdapter(
+ getContext(),
+ presenterSelector,
+ singletons.getClock(),
+ mDvrDataManager,
+ uiFlags);
setAdapter(mRowsAdapter);
mRowsAdapter.start();
- mDvrDataManager = singletons.getDvrDataManager();
mDvrDataManager.addScheduledRecordingListener(this);
mDvrDataManager.addRecordedProgramListener(this);
mEmptyInfoScreenView = (TextView) getActivity().findViewById(R.id.empty_info_screen);
@@ -135,7 +142,6 @@
hideEmptyMessage();
}
}
-
}
@Override
diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java b/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java
index 156d1a7..a10367f 100644
--- a/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java
@@ -20,20 +20,19 @@
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.text.format.DateUtils;
import android.util.Log;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.ClassPresenterSelector;
import com.android.tv.R;
-import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.util.Clock;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
-import com.android.tv.dvr.recorder.ScheduledProgramReaper;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow;
import com.android.tv.util.Utils;
+import com.android.tv.common.flags.UiFlags;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -48,8 +47,8 @@
private static final boolean DEBUG = false;
private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
- private static final int MAX_HISTORY_DAYS = ScheduledProgramReaper.DAYS;
+ private final long mMaxHistoryDays;
private final Context mContext;
private final Clock mClock;
private final DvrDataManager mDvrDataManager;
@@ -57,11 +56,16 @@
private final Map<Long, ScheduledRecording> mRecordedProgramScheduleMap = new HashMap<>();
public DvrHistoryRowAdapter(
- Context context, ClassPresenterSelector classPresenterSelector, Clock clock) {
+ Context context,
+ ClassPresenterSelector classPresenterSelector,
+ Clock clock,
+ DvrDataManager dvrDataManager,
+ UiFlags uiFlags) {
super(classPresenterSelector);
mContext = context;
mClock = clock;
- mDvrDataManager = TvSingletons.getSingletons(mContext).getDvrDataManager();
+ mDvrDataManager = dvrDataManager;
+ mMaxHistoryDays = uiFlags.maxHistoryDays();
mTitles.add(mContext.getString(R.string.dvr_date_today));
mTitles.add(mContext.getString(R.string.dvr_date_yesterday));
}
@@ -78,9 +82,9 @@
List<RecordedProgram> recordedProgramList = mDvrDataManager.getRecordedPrograms();
recordingList.addAll(
- recordedProgramsToScheduledRecordings(recordedProgramList, MAX_HISTORY_DAYS));
- recordingList
- .sort(ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.reversed());
+ recordedProgramsToScheduledRecordings(recordedProgramList, mMaxHistoryDays));
+ recordingList.sort(
+ ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.reversed());
long deadLine = Utils.getFirstMillisecondOfDay(mClock.currentTimeMillis());
for (int i = 0; i < recordingList.size(); ) {
ArrayList<ScheduledRecording> section = new ArrayList<>();
@@ -128,7 +132,7 @@
}
private List<ScheduledRecording> recordedProgramsToScheduledRecordings(
- List<RecordedProgram> programs, int maxDays) {
+ List<RecordedProgram> programs, long maxDays) {
List<ScheduledRecording> result = new ArrayList<>();
for (RecordedProgram recordedProgram : programs) {
ScheduledRecording scheduledRecording =
@@ -142,12 +146,12 @@
@Nullable
private ScheduledRecording recordedProgramsToScheduledRecordings(
- RecordedProgram program, int maxDays) {
+ RecordedProgram program, long maxDays) {
long firstMillisecondToday = Utils.getFirstMillisecondOfDay(mClock.currentTimeMillis());
- if (maxDays
- < Utils.computeDateDifference(
- program.getStartTimeUtcMillis(),
- firstMillisecondToday)) {
+ if (maxDays != 0
+ && maxDays
+ < Utils.computeDateDifference(
+ program.getStartTimeUtcMillis(), firstMillisecondToday)) {
return null;
}
ScheduledRecording scheduledRecording = ScheduledRecording.builder(program).build();
@@ -175,7 +179,7 @@
return;
}
ScheduledRecording schedule =
- recordedProgramsToScheduledRecordings(program, MAX_HISTORY_DAYS);
+ recordedProgramsToScheduledRecordings(program, mMaxHistoryDays);
if (schedule == null) {
return;
}
@@ -248,8 +252,10 @@
for (; index < size(); index++) {
if (get(index) instanceof ScheduleRow) {
ScheduleRow scheduleRow = (ScheduleRow) get(index);
- if (ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.reversed()
- .compare(scheduleRow.getSchedule(), recording) > 0) {
+ if (ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
+ .reversed()
+ .compare(scheduleRow.getSchedule(), recording)
+ > 0) {
break;
}
pre = index;
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
index 82b8563..d0884f9 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
@@ -20,13 +20,15 @@
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.annotation.IntDef;
+
import com.android.tv.R;
import com.android.tv.Starter;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
import com.android.tv.dvr.ui.BigArguments;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
index d97b61f..43a3579 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
@@ -17,7 +17,7 @@
package com.android.tv.dvr.ui.list;
import android.os.Bundle;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.ClassPresenterSelector;
import com.android.tv.R;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter;
diff --git a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
index d376e35..50bc04c 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
@@ -25,20 +25,24 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.transition.Fade;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
+import androidx.leanback.widget.ClassPresenterSelector;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
import com.android.tv.dvr.ui.BigArguments;
+
import java.util.Collections;
import java.util.List;
@@ -53,7 +57,7 @@
"series_schedules_key_series_recording";
/**
* The key for programs which belong to the series recording whose scheduled recording list will
- * be displayed. Type: List<{@link Program}>
+ * be displayed. Type: List<{@link ProgramImpl}>
*/
public static final String SERIES_SCHEDULES_KEY_SERIES_PROGRAMS =
"series_schedules_key_series_programs";
diff --git a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
index d580841..ccb497f 100644
--- a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
+++ b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
@@ -17,7 +17,8 @@
package com.android.tv.dvr.ui.list;
import android.content.Context;
-import com.android.tv.data.Program;
+
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.ScheduledRecording.Builder;
import com.android.tv.dvr.ui.DvrUiHelper;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
index ef4a433..de259f5 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
@@ -22,8 +22,8 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.ClassPresenterSelector;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Log;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
index 11680a0..ff296f4 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
@@ -24,7 +24,7 @@
import android.content.res.Resources;
import android.os.Build;
import android.support.annotation.IntDef;
-import android.support.v17.leanback.widget.RowPresenter;
+import androidx.leanback.widget.RowPresenter;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
index bbddc07..5c687cd 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
@@ -16,8 +16,9 @@
package com.android.tv.dvr.ui.list;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.SeriesRecording;
+
import java.util.List;
/** A base class for the rows for schedules' header. */
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
index 28a44bf..2550eeb 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
@@ -19,7 +19,7 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.support.v17.leanback.widget.RowPresenter;
+import androidx.leanback.widget.RowPresenter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
index 9a9c94e..2c37754 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
@@ -20,19 +20,22 @@
import android.content.Context;
import android.media.tv.TvInputInfo;
import android.os.Build;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.util.ArrayMap;
import android.util.Log;
+
+import androidx.leanback.widget.ClassPresenterSelector;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
import com.android.tv.util.Utils;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
index f24ad2c..4aa1200 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
@@ -16,7 +16,6 @@
package com.android.tv.dvr.ui.playback;
-import android.app.Activity;
import android.content.ContentUris;
import android.content.Intent;
import android.content.res.Configuration;
@@ -28,9 +27,12 @@
import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.util.Utils;
+import dagger.android.AndroidInjection;
+import dagger.android.ContributesAndroidInjector;
+import dagger.android.DaggerActivity;
/** Activity to play a {@link RecordedProgram}. */
-public class DvrPlaybackActivity extends Activity implements OnPinCheckedListener {
+public class DvrPlaybackActivity extends DaggerActivity implements OnPinCheckedListener {
private static final String TAG = "DvrPlaybackActivity";
private static final boolean DEBUG = false;
@@ -39,6 +41,7 @@
@Override
public void onCreate(Bundle savedInstanceState) {
+ AndroidInjection.inject(this);
Starter.start(this);
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
@@ -92,4 +95,16 @@
void setOnPinCheckListener(OnPinCheckedListener listener) {
mOnPinCheckedListener = listener;
}
+
+ /**
+ * Exports {@link DvrPlaybackActivity} for Dagger codegen to create the appropriate injector.
+ */
+ @dagger.Module
+ public abstract static class Module {
+ @ContributesAndroidInjector
+ abstract DvrPlaybackActivity contributesDvrPlaybackActivity();
+
+ @ContributesAndroidInjector
+ abstract DvrPlaybackOverlayFragment contributesDvrPlaybackOverlayFragment();
+ }
}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
index 791d26b..35c5d4e 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
@@ -26,15 +26,15 @@
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.media.PlaybackControlGlue;
-import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRow.ClosedCaptioningAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.RowPresenter;
+import androidx.leanback.media.PlaybackControlGlue;
+import androidx.leanback.widget.AbstractDetailsDescriptionPresenter;
+import androidx.leanback.widget.Action;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.PlaybackControlsRow;
+import androidx.leanback.widget.PlaybackControlsRow.ClosedCaptioningAction;
+import androidx.leanback.widget.PlaybackControlsRow.MultiAction;
+import androidx.leanback.widget.PlaybackControlsRowPresenter;
+import androidx.leanback.widget.RowPresenter;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
index 1059e85..0c96cac 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
@@ -26,24 +26,25 @@
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
-import android.support.v17.leanback.app.PlaybackFragment;
-import android.support.v17.leanback.app.PlaybackFragmentGlueHost;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SinglePresenterSelector;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
+import androidx.leanback.app.PlaybackFragment;
+import androidx.leanback.app.PlaybackFragmentGlueHost;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.BaseOnItemViewClickedListener;
+import androidx.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.HeaderItem;
+import androidx.leanback.widget.ListRow;
+import androidx.leanback.widget.Presenter;
+import androidx.leanback.widget.RowPresenter;
+import androidx.leanback.widget.SinglePresenterSelector;
import com.android.tv.R;
-import com.android.tv.TvSingletons;
-import com.android.tv.data.BaseProgram;
+import com.android.tv.audio.AudioManagerHelper;
+import com.android.tv.common.buildtype.HasBuildType.BuildType;
+import com.android.tv.data.api.BaseProgram;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.data.RecordedProgram;
@@ -55,8 +56,11 @@
import com.android.tv.util.TvSettings;
import com.android.tv.util.TvTrackInfoUtils;
import com.android.tv.util.Utils;
+import dagger.android.AndroidInjection;
+import com.android.tv.common.flags.LegacyFlags;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
public class DvrPlaybackOverlayFragment extends PlaybackFragment {
// TODO: Handles audio focus. Deals with block and ratings.
@@ -75,7 +79,7 @@
private ArrayObjectAdapter mRowsAdapter;
private SortedArrayAdapter<BaseProgram> mRelatedRecordingsRowAdapter;
private DvrPlaybackCardPresenter mRelatedRecordingCardPresenter;
- private DvrDataManager mDvrDataManager;
+ private AudioManagerHelper mAudioManagerHelper;
private AppLayerTvView mTvView;
private View mBlockScreenView;
private ListRow mRelatedRecordingsRow;
@@ -97,9 +101,24 @@
}
};
+ @Inject DvrDataManager mDvrDataManager;
+ @Inject LegacyFlags mLegacyFlags;
+ @Inject BuildType buildType;
+
+ @Override
+ public void onAttach(Context context) {
+ if (DEBUG) {
+ Log.d(TAG, "onAttach");
+ }
+ AndroidInjection.inject(this);
+ super.onAttach(context);
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate");
+ if (DEBUG) {
+ Log.d(TAG, "onCreate");
+ }
super.onCreate(savedInstanceState);
mVerticalPaddingBase =
getActivity()
@@ -115,7 +134,6 @@
.getResources()
.getDimensionPixelOffset(
R.dimen.dvr_playback_overlay_padding_top_no_secondary_row);
- mDvrDataManager = TvSingletons.getSingletons(getActivity()).getDvrDataManager();
if (!mDvrDataManager.isRecordedProgramLoadFinished()) {
mDvrDataManager.addRecordedProgramLoadFinishedListener(
new DvrDataManager.OnRecordedProgramLoadFinishedListener() {
@@ -153,6 +171,8 @@
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mTvView = getActivity().findViewById(R.id.dvr_tv_view);
+ mTvView.setUseSecureSurface(
+ buildType != BuildType.ENG && !mLegacyFlags.enableDeveloperFeatures());
mBlockScreenView = getActivity().findViewById(R.id.block_screen);
mDvrPlayer = new DvrPlayer(mTvView, getActivity());
mMediaSessionHelper =
@@ -240,13 +260,16 @@
setFadingEnabled(false);
long programId =
((RecordedProgram) itemViewHolder.view.getTag()).getId();
- if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
+ if (DEBUG) {
+ Log.d(TAG, "Play Related Recording:" + programId);
+ }
Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
getContext().startActivity(intent);
}
}
});
+ mAudioManagerHelper = new AudioManagerHelper(getActivity(), mDvrPlayer.getView());
if (mProgram != null) {
setUpRows();
preparePlayback(getActivity().getIntent());
@@ -255,7 +278,9 @@
@Override
public void onPause() {
- if (DEBUG) Log.d(TAG, "onPause");
+ if (DEBUG) {
+ Log.d(TAG, "onPause");
+ }
super.onPause();
if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING
|| mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_REWINDING) {
@@ -270,9 +295,12 @@
@Override
public void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy");
+ if (DEBUG) {
+ Log.d(TAG, "onDestroy");
+ }
mPlaybackControlHelper.unregisterCallback();
mMediaSessionHelper.release();
+ mAudioManagerHelper.abandonAudioFocus();
mRelatedRecordingCardPresenter.unbindAllViewHolders();
mDvrPlayer.release();
super.onDestroy();
@@ -416,6 +444,7 @@
private void preparePlayback(Intent intent) {
mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent));
mPlaybackControlHelper.updateSecondaryRow(false, false);
+ mAudioManagerHelper.requestAudioFocus();
getActivity().getMediaController().getTransportControls().prepare();
updateRelatedRecordingsRow();
}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
index b4481df..95858e3 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
@@ -19,8 +19,8 @@
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidedAction;
import android.text.TextUtils;
import android.transition.Transition;
import android.view.LayoutInflater;
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
index d14646b..e8325e1 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
@@ -326,6 +326,11 @@
return mProgram;
}
+ /** Returns the DVR tv view. */
+ public DvrTvView getView() {
+ return mTvView;
+ }
+
/** Returns the currrent playback posistion in msecs. */
public long getPlaybackPosition() {
return mTimeShiftCurrentPositionMs;
diff --git a/src/com/android/tv/features/TvFeatures.java b/src/com/android/tv/features/TvFeatures.java
index 208d53f..f7a39ef 100644
--- a/src/com/android/tv/features/TvFeatures.java
+++ b/src/com/android/tv/features/TvFeatures.java
@@ -27,14 +27,11 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
-import android.support.annotation.VisibleForTesting;
-import com.android.tv.common.experiments.Experiments;
+
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.common.feature.ExperimentFeature;
import com.android.tv.common.feature.Feature;
import com.android.tv.common.feature.FeatureUtils;
import com.android.tv.common.feature.FlagFeature;
-import com.android.tv.common.feature.PropertyFeature;
import com.android.tv.common.feature.Sdk;
import com.android.tv.common.feature.TestableFeature;
import com.android.tv.common.flags.has.HasUiFlags;
@@ -42,7 +39,7 @@
import com.android.tv.common.util.PermissionUtils;
/**
- * List of {@link Feature} for the Live TV App.
+ * List of {@link Feature} for the TV app.
*
* <p>Remove the {@code Feature} once it is launched.
*/
@@ -51,16 +48,6 @@
/** When enabled store network affiliation information to TV provider */
public static final Feature STORE_NETWORK_AFFILIATION = ENG_ONLY_FEATURE;
- /** When enabled use system setting for turning on analytics. */
- public static final Feature ANALYTICS_OPT_IN =
- ExperimentFeature.from(Experiments.ENABLE_ANALYTICS_VIA_CHECKBOX);
- /**
- * Analytics that include sensitive information such as channel or program identifiers.
- *
- * <p>See <a href="http://b/22062676">b/22062676</a>
- */
- public static final Feature ANALYTICS_V2 = and(ON, ANALYTICS_OPT_IN);
-
private static final Feature TV_PROVIDER_ALLOWS_INSERT_TO_PROGRAM_TABLE =
or(Sdk.AT_LEAST_O, PartnerFeatures.TVPROVIDER_ALLOWS_SYSTEM_INSERTS_TO_PROGRAM_TABLE);
@@ -114,8 +101,5 @@
/** Use input blacklist to disable partner's tuner input. */
public static final Feature USE_PARTNER_INPUT_BLACKLIST = ON;
- @VisibleForTesting
- public static final Feature TEST_FEATURE = PropertyFeature.create("test_feature", false);
-
private TvFeatures() {}
}
diff --git a/src/com/android/tv/guide/GenreListAdapter.java b/src/com/android/tv/guide/GenreListAdapter.java
index b4baf42..995b053 100644
--- a/src/com/android/tv/guide/GenreListAdapter.java
+++ b/src/com/android/tv/guide/GenreListAdapter.java
@@ -18,7 +18,7 @@
import android.content.Context;
import android.support.annotation.MainThread;
-import android.support.v7.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java
index caafb04..96e161c 100644
--- a/src/com/android/tv/guide/ProgramGrid.java
+++ b/src/com/android/tv/guide/ProgramGrid.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.support.v17.leanback.widget.VerticalGridView;
+import androidx.leanback.widget.VerticalGridView;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Range;
@@ -256,8 +256,19 @@
scrollToPosition(getAdapter().getItemCount() - 1);
return null;
} else if (getSelectedPosition() == getAdapter().getItemCount() - 1) {
- scrollToPosition(0);
- return null;
+ int itemCount = getLayoutManager().getItemCount();
+ int childCount = getChildCount();
+ // b/129466363 For an item which overalps with previous layout GridLayoutManager
+ // will scroll to first child of current layout, instead of going to previous one.
+ // smoothscrollToPosition will invalidate all layouts and scroll to position 0.
+ // This condition checks for an item which overlaps with the first layout
+ if (itemCount > 2 * (childCount + 1) || itemCount <= childCount) {
+ scrollToPosition(0);
+ return null;
+ } else {
+ smoothScrollToPosition(0);
+ return getChildAt(0);
+ }
}
return focused;
}
diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java
index bc1b11b..8ae61e8 100644
--- a/src/com/android/tv/guide/ProgramGuide.java
+++ b/src/com/android/tv/guide/ProgramGuide.java
@@ -32,10 +32,7 @@
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.OnChildSelectedListener;
-import android.support.v17.leanback.widget.SearchOrbView;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.View.MeasureSpec;
@@ -44,6 +41,11 @@
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+
+import androidx.leanback.widget.OnChildSelectedListener;
+import androidx.leanback.widget.SearchOrbView;
+import androidx.leanback.widget.VerticalGridView;
+
import com.android.tv.ChannelTuner;
import com.android.tv.MainActivity;
import com.android.tv.R;
@@ -65,7 +67,9 @@
import com.android.tv.ui.hideable.AutoHideScheduler;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-import com.android.tv.common.flags.BackendKnobsFlags;
+
+import com.android.tv.common.flags.UiFlags;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -112,6 +116,7 @@
private final int mAnimationDuration;
private final int mDetailPadding;
private final SearchOrbView mSearchOrb;
+ private final UiFlags mUiFlags;
private int mCurrentTimeIndicatorWidth;
private final View mContainer;
@@ -185,15 +190,14 @@
mActivity = activity;
TvSingletons singletons = TvSingletons.getSingletons(mActivity);
mPerformanceMonitor = singletons.getPerformanceMonitor();
- BackendKnobsFlags backendKnobsFlags = singletons.getBackendKnobs();
+ mUiFlags = singletons.getUiFlags();
mProgramManager =
new ProgramManager(
tvInputManagerHelper,
channelDataManager,
programDataManager,
dvrDataManager,
- dvrScheduleManager,
- backendKnobsFlags);
+ dvrScheduleManager);
mChannelTuner = channelTuner;
mTracker = tracker;
mPreShowRunnable = preShowRunnable;
@@ -261,7 +265,7 @@
}
});
mSidePanelGridView.setOnChildSelectedListener(
- new android.support.v17.leanback.widget.OnChildSelectedListener() {
+ new androidx.leanback.widget.OnChildSelectedListener() {
@Override
public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) {
mSearchOrb.animate().alpha(i == 0 ? 1.0f : 0.0f);
@@ -282,7 +286,8 @@
res.getInteger(R.integer.max_recycled_view_pool_epg_header_row_item));
mTimelineRow.setAdapter(mTimeListAdapter);
- ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity, this);
+ ProgramTableAdapter programTableAdapter =
+ new ProgramTableAdapter(mActivity, this, mUiFlags);
programTableAdapter.registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {
@Override
diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java
index a46beab..65b7641 100644
--- a/src/com/android/tv/guide/ProgramItemView.java
+++ b/src/com/android/tv/guide/ProgramItemView.java
@@ -23,6 +23,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
+import android.os.Build;
import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -34,6 +35,7 @@
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
+
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvSingletons;
@@ -41,17 +43,22 @@
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.util.Clock;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.guide.ProgramManager.TableEntry;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
+
+import dagger.android.HasAndroidInjector;
+
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.TimeUnit;
+import javax.inject.Inject;
+
public class ProgramItemView extends TextView {
private static final String TAG = "ProgramItemView";
@@ -73,8 +80,8 @@
private static TextAppearanceSpan sGrayedOutEpisodeTitleStyle;
private final DvrManager mDvrManager;
- private final Clock mClock;
- private final ChannelDataManager mChannelDataManager;
+ @Inject Clock mClock;
+ @Inject ChannelDataManager mChannelDataManager;
private ProgramGuide mProgramGuide;
private TableEntry mTableEntry;
private int mMaxWidthForRipple;
@@ -202,12 +209,11 @@
public ProgramItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ ((HasAndroidInjector) context).androidInjector().inject(this);
setOnClickListener(ON_CLICKED);
setOnFocusChangeListener(ON_FOCUS_CHANGED);
TvSingletons singletons = TvSingletons.getSingletons(getContext());
mDvrManager = singletons.getDvrManager();
- mChannelDataManager = singletons.getChannelDataManager();
- mClock = singletons.getClock();
}
private void initIfNeeded() {
@@ -530,6 +536,11 @@
}
private static int getStateCount(StateListDrawable stateListDrawable) {
+ /* Begin_AOSP_Before_Q_Comment_Out */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ return stateListDrawable.getStateCount();
+ }
+ /* End_AOSP_Before_Q_Comment_Out */
try {
Object stateCount =
StateListDrawable.class
@@ -546,6 +557,11 @@
}
private static Drawable getStateDrawable(StateListDrawable stateListDrawable, int index) {
+ /* Begin_AOSP_Before_Q_Comment_Out */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ return stateListDrawable.getStateDrawable(index);
+ }
+ /* End_AOSP_Before_Q_Comment_Out */
try {
Object drawable =
StateListDrawable.class
diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java
index 397bacf..68ae43e 100644
--- a/src/com/android/tv/guide/ProgramListAdapter.java
+++ b/src/com/android/tv/guide/ProgramListAdapter.java
@@ -17,7 +17,7 @@
package com.android.tv.guide;
import android.content.res.Resources;
-import android.support.v7.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java
index 3a5a4a0..516a4d9 100644
--- a/src/com/android/tv/guide/ProgramManager.java
+++ b/src/com/android/tv/guide/ProgramManager.java
@@ -21,18 +21,20 @@
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.Log;
+
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.GenreItems;
-import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.DvrScheduleManager.OnConflictStateChangeListener;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-import com.android.tv.common.flags.BackendKnobsFlags;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -60,7 +62,6 @@
private final ProgramDataManager mProgramDataManager;
private final DvrDataManager mDvrDataManager; // Only set if DVR is enabled
private final DvrScheduleManager mDvrScheduleManager;
- private final BackendKnobsFlags mBackendKnobsFlags;
private long mStartUtcMillis;
private long mEndUtcMillis;
@@ -124,16 +125,8 @@
}
@Override
- public void onSingleChannelUpdated(long channelId) {
- boolean parentalControlsEnabled =
- mTvInputManagerHelper
- .getParentalControlSettings()
- .isParentalControlsEnabled();
- // Inline the updating of the mChannelIdEntriesMap here so we can only call
- // getParentalControlSettings once.
- List<TableEntry> entries =
- createProgramEntries(channelId, parentalControlsEnabled);
- mChannelIdEntriesMap.put(channelId, entries);
+ public void onChannelUpdated() {
+ updateTableEntriesWithoutNotification(false);
notifyTableEntriesUpdated();
}
};
@@ -215,14 +208,12 @@
ChannelDataManager channelDataManager,
ProgramDataManager programDataManager,
@Nullable DvrDataManager dvrDataManager,
- @Nullable DvrScheduleManager dvrScheduleManager,
- BackendKnobsFlags backendKnobsFlags) {
+ @Nullable DvrScheduleManager dvrScheduleManager) {
mTvInputManagerHelper = tvInputManagerHelper;
mChannelDataManager = channelDataManager;
mProgramDataManager = programDataManager;
mDvrDataManager = dvrDataManager;
mDvrScheduleManager = dvrScheduleManager;
- mBackendKnobsFlags = backendKnobsFlags;
}
void programGuideVisibilityChanged(boolean visible) {
@@ -251,7 +242,6 @@
mDvrScheduleManager.removeOnConflictStateChangeListener(
mOnConflictStateChangeListener);
}
- mChannelIdEntriesMap.clear();
}
}
@@ -426,14 +416,12 @@
}
/**
- * Returns an entry as {@link Program} for a given {@code channelId} and {@code index} of
- * entries within the currently managed time range. Returned {@link Program} can be a dummy one
- * (e.g., whose channelId is INVALID_ID), when it corresponds to a gap between programs.
+ * Returns an entry as {@link ProgramImpl} for a given {@code channelId} and {@code index} of
+ * entries within the currently managed time range. Returned {@link ProgramImpl} can be a dummy
+ * one (e.g., whose channelId is INVALID_ID), when it corresponds to a gap between programs.
*/
TableEntry getTableEntry(long channelId, int index) {
- if (mBackendKnobsFlags.enablePartialProgramFetch()) {
- mProgramDataManager.prefetchChannel(channelId);
- }
+ mProgramDataManager.prefetchChannel(channelId, index);
return mChannelIdEntriesMap.get(channelId).get(index);
}
@@ -707,7 +695,7 @@
/**
* Entry for program guide table. An "entry" can be either an actual program or a gap between
* programs. This is needed for {@link ProgramListAdapter} because {@link
- * android.support.v17.leanback.widget.HorizontalGridView} ignores margins between items.
+ * androidx.leanback.widget.HorizontalGridView} ignores margins between items.
*/
static class TableEntry {
/** Channel ID which this entry is included. */
@@ -737,7 +725,7 @@
private TableEntry(
long channelId,
- Program program,
+ ProgramImpl program,
long entryStartUtcMillis,
long entryEndUtcMillis,
boolean isBlocked) {
@@ -759,7 +747,7 @@
mIsBlocked = isBlocked;
}
- /** A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}. */
+ /** A stable id useful for {@link androidx.recyclerview.widget.RecyclerView.Adapter}. */
long getId() {
// using a negative entryEndUtcMillis keeps it from conflicting with program Id
return program != null ? program.getId() : -entryEndUtcMillis;
diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java
index 3317c15..6f8f31c 100644
--- a/src/com/android/tv/guide/ProgramRow.java
+++ b/src/com/android/tv/guide/ProgramRow.java
@@ -18,7 +18,7 @@
import android.content.Context;
import android.graphics.Rect;
-import android.support.v7.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Range;
diff --git a/src/com/android/tv/guide/ProgramRowAccessibilityDelegate.java b/src/com/android/tv/guide/ProgramRowAccessibilityDelegate.java
index 5e498be..a6a4624 100644
--- a/src/com/android/tv/guide/ProgramRowAccessibilityDelegate.java
+++ b/src/com/android/tv/guide/ProgramRowAccessibilityDelegate.java
@@ -17,8 +17,8 @@
package com.android.tv.guide;
import android.os.Bundle;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java
index 7576bf5..aed8b90 100644
--- a/src/com/android/tv/guide/ProgramTableAdapter.java
+++ b/src/com/android/tv/guide/ProgramTableAdapter.java
@@ -28,8 +28,8 @@
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.RecycledViewPool;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
@@ -47,13 +47,14 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.util.CommonUtils;
-import com.android.tv.data.Program;
-import com.android.tv.data.Program.CriticScore;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+import com.android.tv.data.api.Program.CriticScore;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
@@ -67,6 +68,8 @@
import com.android.tv.util.images.ImageLoader.ImageLoaderCallback;
import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask;
+import com.android.tv.common.flags.UiFlags;
+
import java.util.ArrayList;
import java.util.List;
@@ -109,10 +112,11 @@
private final String mRecordingInProgressText;
private final int mDvrPaddingStartWithTrack;
private final int mDvrPaddingStartWithOutTrack;
+ private final UiFlags mUiFlags;
private RecyclerView mRecyclerView;
- ProgramTableAdapter(Context context, ProgramGuide programGuide) {
+ ProgramTableAdapter(Context context, ProgramGuide programGuide, UiFlags uiFlags) {
mContext = context;
mAccessibilityManager =
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -126,6 +130,7 @@
}
mProgramGuide = programGuide;
mProgramManager = programGuide.getProgramManager();
+ mUiFlags = uiFlags;
Resources res = context.getResources();
mChannelLogoWidth =
@@ -656,6 +661,35 @@
mDvrIndicator.setVisibility(View.GONE);
}
+ if (mUiFlags.enableCriticRatings()) {
+ // display critic scores if any exist
+ List<CriticScore> criticScores = program.getCriticScores();
+ if (criticScores != null) {
+ // inflate more critic score views if required
+ if (criticScores.size() > mCriticScoreViews.size()) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ LinearLayout layout =
+ (LinearLayout)
+ inflater.inflate(
+ R.layout.program_guide_critic_score_layout,
+ null);
+ mCriticScoreViews.add(layout);
+ }
+ // fill critic score views and add to layout
+ for (int i = 0; i < criticScores.size(); i++) {
+ View criticScoreView = mCriticScoreViews.get(i);
+ ViewParent previousParentView = criticScoreView.getParent();
+ if (previousParentView != null
+ && previousParentView instanceof ViewGroup) {
+ ((ViewGroup) previousParentView).removeView(criticScoreView);
+ }
+ updateCriticScoreView(
+ this, program.getId(), criticScores.get(i), criticScoreView);
+ mCriticScoresLayout.addView(mCriticScoreViews.get(i));
+ }
+ }
+ }
+
if (blockedRating == null) {
mBlockView.setVisibility(View.GONE);
updateTextView(mDescriptionView, program.getDescription());
diff --git a/src/com/android/tv/guide/TimeListAdapter.java b/src/com/android/tv/guide/TimeListAdapter.java
index 9c10c95..62fec69 100644
--- a/src/com/android/tv/guide/TimeListAdapter.java
+++ b/src/com/android/tv/guide/TimeListAdapter.java
@@ -17,7 +17,7 @@
package com.android.tv.guide;
import android.content.res.Resources;
-import android.support.v7.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/src/com/android/tv/guide/TimelineGridView.java b/src/com/android/tv/guide/TimelineGridView.java
index c4922b7..2d25787 100644
--- a/src/com/android/tv/guide/TimelineGridView.java
+++ b/src/com/android/tv/guide/TimelineGridView.java
@@ -17,8 +17,8 @@
package com.android.tv.guide;
import android.content.Context;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
diff --git a/src/com/android/tv/menu/ActionCardView.java b/src/com/android/tv/menu/ActionCardView.java
index 3ecd5f5..0e789c6 100644
--- a/src/com/android/tv/menu/ActionCardView.java
+++ b/src/com/android/tv/menu/ActionCardView.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -93,6 +94,13 @@
}
}
+ /** Request focus and accessibility focus on card view. */
+ @Override
+ public boolean requestFocusWithAccessibility() {
+ return requestFocus() &&
+ performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
@Override
public void onRecycled() {}
}
diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java
index fd93c31..49d32fe 100644
--- a/src/com/android/tv/menu/AppLinkCardView.java
+++ b/src/com/android/tv/menu/AppLinkCardView.java
@@ -26,13 +26,13 @@
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
-import android.support.v7.graphics.Palette;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.palette.graphics.Palette;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.data.api.Channel;
diff --git a/src/com/android/tv/menu/BaseCardView.java b/src/com/android/tv/menu/BaseCardView.java
index 3a94ebb..ed78cb7 100644
--- a/src/com/android/tv/menu/BaseCardView.java
+++ b/src/com/android/tv/menu/BaseCardView.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.tv.R;
@@ -135,6 +136,13 @@
}
}
+ /** Request focus and accessibility focus on card view. */
+ @Override
+ public boolean requestFocusWithAccessibility() {
+ return requestFocus() &&
+ performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
/** Sets text of this card view. */
public void setText(int resId) {
if (mTextResId != resId) {
diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java
index 76056ee..7fe5e49 100644
--- a/src/com/android/tv/menu/ChannelCardView.java
+++ b/src/com/android/tv/menu/ChannelCardView.java
@@ -26,12 +26,14 @@
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.util.images.ImageLoader;
+
import java.util.Objects;
/** A view to render channel card. */
diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
index 9cecb9c..3a50230 100644
--- a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
+++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
@@ -23,13 +23,15 @@
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.util.Log;
+
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.WeakHandler;
import com.android.tv.data.ChannelImpl;
-import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+
import java.util.List;
/** A poster image prefetcher to show the program poster art in the Channels row faster. */
diff --git a/src/com/android/tv/menu/ChannelsRow.java b/src/com/android/tv/menu/ChannelsRow.java
index 7d03bf2..dbfc782 100644
--- a/src/com/android/tv/menu/ChannelsRow.java
+++ b/src/com/android/tv/menu/ChannelsRow.java
@@ -73,6 +73,7 @@
mTvRecommendation = null;
}
mChannelsPosterPrefetcher.cancel();
+ mChannelsAdapter.release();
}
/** Handle the update event of the recent channel. */
diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java
index 4a9e476..e6b6103 100644
--- a/src/com/android/tv/menu/ChannelsRowAdapter.java
+++ b/src/com/android/tv/menu/ChannelsRowAdapter.java
@@ -47,6 +47,7 @@
private final int mMaxCount;
private final int mMinCount;
private final ChannelChanger mChannelChanger;
+ private final AccessibilityManager mAccessibilityManager;
private boolean mShowChannelUpDown;
@@ -66,10 +67,9 @@
mMaxCount = maxCount;
setHasStableIds(true);
mChannelChanger = (ChannelChanger) (context);
- AccessibilityManager accessibilityManager =
- context.getSystemService(AccessibilityManager.class);
- mShowChannelUpDown = accessibilityManager.isEnabled();
- accessibilityManager.addAccessibilityStateChangeListener(this);
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ mShowChannelUpDown = mAccessibilityManager.isEnabled();
+ mAccessibilityManager.addAccessibilityStateChangeListener(this);
}
@Override
@@ -316,4 +316,10 @@
mShowChannelUpDown = enabled;
update();
}
+
+ @Override
+ public void release() {
+ mAccessibilityManager.removeAccessibilityStateChangeListener(this);
+ super.release();
+ }
}
diff --git a/src/com/android/tv/menu/ItemListRowView.java b/src/com/android/tv/menu/ItemListRowView.java
index 7042324..cc6d23c 100644
--- a/src/com/android/tv/menu/ItemListRowView.java
+++ b/src/com/android/tv/menu/ItemListRowView.java
@@ -17,9 +17,9 @@
package com.android.tv.menu;
import android.content.Context;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.OnChildSelectedListener;
-import android.support.v7.widget.RecyclerView;
+import androidx.leanback.widget.HorizontalGridView;
+import androidx.leanback.widget.OnChildSelectedListener;
+import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -44,6 +44,8 @@
void onSelected();
void onDeselected();
+
+ boolean requestFocusWithAccessibility();
}
private HorizontalGridView mListView;
@@ -114,6 +116,13 @@
}
}
+ @Override
+ protected void requestChildFocus() {
+ if (mSelectedCard != null) {
+ mSelectedCard.requestFocusWithAccessibility();
+ }
+ }
+
public abstract static class ItemListAdapter<T>
extends RecyclerView.Adapter<ItemListAdapter.MyViewHolder> {
private final MainActivity mMainActivity;
diff --git a/src/com/android/tv/menu/Menu.java b/src/com/android/tv/menu/Menu.java
index 6bdbf87..0687441 100644
--- a/src/com/android/tv/menu/Menu.java
+++ b/src/com/android/tv/menu/Menu.java
@@ -23,7 +23,7 @@
import android.content.res.Resources;
import android.support.annotation.IntDef;
import android.support.annotation.VisibleForTesting;
-import android.support.v17.leanback.widget.HorizontalGridView;
+import androidx.leanback.widget.HorizontalGridView;
import android.util.Log;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import com.android.tv.ChannelTuner;
diff --git a/src/com/android/tv/menu/MenuLayoutManager.java b/src/com/android/tv/menu/MenuLayoutManager.java
index a600f70..8f95db7 100644
--- a/src/com/android/tv/menu/MenuLayoutManager.java
+++ b/src/com/android/tv/menu/MenuLayoutManager.java
@@ -25,15 +25,15 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.support.annotation.UiThread;
-import android.support.v4.view.animation.FastOutLinearInInterpolator;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
-import android.support.v4.view.animation.LinearOutSlowInInterpolator;
-import android.support.v7.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.util.Property;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;
+import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
+import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.util.Utils;
diff --git a/src/com/android/tv/menu/MenuRow.java b/src/com/android/tv/menu/MenuRow.java
index 8dc12ba..0945a0c 100644
--- a/src/com/android/tv/menu/MenuRow.java
+++ b/src/com/android/tv/menu/MenuRow.java
@@ -31,6 +31,8 @@
private MenuRowView mMenuRowView;
+ private boolean mIsReselected = false;
+
// TODO: Check if the heightResId is really necessary.
public MenuRow(Context context, Menu menu, int titleResId, int heightResId) {
this(context, menu, context.getString(titleResId), heightResId);
@@ -100,4 +102,19 @@
public boolean hideTitleWhenSelected() {
return false;
}
+
+ /**
+ * Sets if menu row is reselected.
+ *
+ * @param isReselected {@code true} if row is reselected;
+ * else {@code false}.
+ */
+ public void setIsReselected(boolean isReselected) {
+ mIsReselected = isReselected;
+ }
+
+ /** Returns true if row is reselected. */
+ public boolean isReselected() {
+ return mIsReselected;
+ }
}
diff --git a/src/com/android/tv/menu/MenuRowFactory.java b/src/com/android/tv/menu/MenuRowFactory.java
index 048d725..a3837a1 100644
--- a/src/com/android/tv/menu/MenuRowFactory.java
+++ b/src/com/android/tv/menu/MenuRowFactory.java
@@ -24,6 +24,7 @@
import com.android.tv.common.customization.CustomAction;
import com.android.tv.common.customization.CustomizationManager;
import com.android.tv.ui.TunableTvView;
+import com.android.tv.common.flags.LegacyFlags;
import java.util.List;
/** A factory class to create menu rows. */
@@ -31,12 +32,15 @@
private final MainActivity mMainActivity;
private final TunableTvView mTvView;
private final CustomizationManager mCustomizationManager;
+ private final LegacyFlags mLegacyFlags;
/** A constructor. */
- public MenuRowFactory(MainActivity mainActivity, TunableTvView tvView) {
+ public MenuRowFactory(
+ MainActivity mainActivity, TunableTvView tvView, LegacyFlags mLegacyFlags) {
mMainActivity = mainActivity;
mTvView = tvView;
mCustomizationManager = new CustomizationManager(mainActivity);
+ this.mLegacyFlags = mLegacyFlags;
mCustomizationManager.initialize();
}
@@ -60,7 +64,8 @@
return new TvOptionsRow(
mMainActivity,
menu,
- mCustomizationManager.getCustomActions(CustomizationManager.ID_OPTIONS_ROW));
+ mCustomizationManager.getCustomActions(CustomizationManager.ID_OPTIONS_ROW),
+ mLegacyFlags);
}
return null;
}
@@ -70,13 +75,17 @@
/** The ID of the row. */
public static final String ID = TvOptionsRow.class.getName();
- private TvOptionsRow(Context context, Menu menu, List<CustomAction> customActions) {
+ private TvOptionsRow(
+ Context context,
+ Menu menu,
+ List<CustomAction> customActions,
+ LegacyFlags legacyFlags) {
super(
context,
menu,
R.string.menu_title_options,
R.dimen.action_card_height,
- new TvOptionsRowAdapter(context, customActions));
+ new TvOptionsRowAdapter(context, customActions, legacyFlags));
}
}
diff --git a/src/com/android/tv/menu/MenuRowView.java b/src/com/android/tv/menu/MenuRowView.java
index a064f35..e09a4ef 100644
--- a/src/com/android/tv/menu/MenuRowView.java
+++ b/src/com/android/tv/menu/MenuRowView.java
@@ -25,6 +25,7 @@
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.tv.R;
@@ -89,6 +90,18 @@
float textSizeDeselected =
res.getDimensionPixelSize(R.dimen.menu_row_title_text_size_deselected);
mTitleViewScaleSelected = textSizeSelected / textSizeDeselected;
+ this.setAccessibilityDelegate(
+ new AccessibilityDelegate() {
+ @Override
+ public void sendAccessibilityEvent(View host, int eventType) {
+ super.sendAccessibilityEvent(host, eventType);
+ if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED &&
+ !mRow.isReselected()) {
+ requestChildFocus();
+ }
+ }
+ }
+ );
}
@Override
@@ -177,6 +190,9 @@
mLastFocusView = v;
}
+ /** Subclasses should implement this to request focus on child. */
+ protected abstract void requestChildFocus();
+
/**
* Called when the focus of a child view is changed. The inherited class should override this
* method instead of calling {@link
diff --git a/src/com/android/tv/menu/MenuView.java b/src/com/android/tv/menu/MenuView.java
index f5fec00..add4a77 100644
--- a/src/com/android/tv/menu/MenuView.java
+++ b/src/com/android/tv/menu/MenuView.java
@@ -250,41 +250,42 @@
// The bounds of the views move and overlap with each other during the animation. In this
// situation, the framework can't perform the correct focus navigation. So the menu view
// should search by itself.
- if (direction == View.FOCUS_UP) {
- View newView = super.focusSearch(focused, direction);
- MenuRowView oldfocusedParent = getParentMenuRowView(focused);
- MenuRowView newFocusedParent = getParentMenuRowView(newView);
- int selectedPosition = mLayoutManager.getSelectedPosition();
- if (newFocusedParent != oldfocusedParent) {
- // The focus leaves from the current menu row view.
- for (int i = selectedPosition - 1; i >= 0; --i) {
- MenuRowView view = mMenuRowViews.get(i);
- if (view.getVisibility() == View.VISIBLE) {
- return view;
- }
- }
- }
- return newView;
- } else if (direction == View.FOCUS_DOWN) {
- View newView = super.focusSearch(focused, direction);
- MenuRowView oldfocusedParent = getParentMenuRowView(focused);
- MenuRowView newFocusedParent = getParentMenuRowView(newView);
- int selectedPosition = mLayoutManager.getSelectedPosition();
- if (newFocusedParent != oldfocusedParent) {
- // The focus leaves from the current menu row view.
- int count = mMenuRowViews.size();
- for (int i = selectedPosition + 1; i < count; ++i) {
- MenuRowView view = mMenuRowViews.get(i);
- if (view.getVisibility() == View.VISIBLE) {
- return view;
- }
- }
- }
- return newView;
+ if (direction == View.FOCUS_UP || direction == View.FOCUS_DOWN) {
+ return getUpDownFocus(focused, direction);
}
return super.focusSearch(focused, direction);
}
+ private View getUpDownFocus(View focused, int direction) {
+ View newView = super.focusSearch(focused, direction);
+ MenuRowView oldfocusedParent = getParentMenuRowView(focused);
+ MenuRowView newFocusedParent = getParentMenuRowView(newView);
+ int selectedPosition = mLayoutManager.getSelectedPosition();
+ int start, delta;
+ if (direction == View.FOCUS_UP) {
+ start = selectedPosition - 1;
+ delta = -1;
+ } else {
+ start = selectedPosition + 1;
+ delta = 1;
+ }
+ if (newFocusedParent != oldfocusedParent) {
+ // The focus leaves from the current menu row view.
+ int count = mMenuRowViews.size();
+ int i = start;
+ while (i < count && i >= 0) {
+ MenuRowView view = mMenuRowViews.get(i);
+ if (view.getVisibility() == View.VISIBLE) {
+ mMenuRows.get(i).setIsReselected(false);
+ return view;
+ }
+ i += delta;
+ }
+ }
+ mMenuRows.get(selectedPosition).setIsReselected(true);
+ return newView;
+ }
+
private MenuRowView getParentMenuRowView(View view) {
if (view == null) {
return null;
diff --git a/src/com/android/tv/menu/PlayControlsButton.java b/src/com/android/tv/menu/PlayControlsButton.java
index ac3292a..1b85d63 100644
--- a/src/com/android/tv/menu/PlayControlsButton.java
+++ b/src/com/android/tv/menu/PlayControlsButton.java
@@ -22,6 +22,7 @@
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@@ -146,4 +147,11 @@
mIcon.setAlpha(enabled ? ALPHA_ENABLED : ALPHA_DISABLED);
mLabel.setEnabled(enabled);
}
+
+ /** Request focus and accessibility focus to the button */
+ public boolean requestFocusWithAccessibility() {
+ return mButton.requestFocus() &&
+ mButton.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
}
diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java
index 0ce74ae..5dde3be 100644
--- a/src/com/android/tv/menu/PlayControlsRowView.java
+++ b/src/com/android/tv/menu/PlayControlsRowView.java
@@ -24,6 +24,7 @@
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
+
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TimeShiftManager;
@@ -31,8 +32,8 @@
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
@@ -487,6 +488,11 @@
}
}
+ @Override
+ protected void requestChildFocus() {
+ mPlayPauseButton.requestFocusWithAccessibility();
+ }
+
/** Updates the view contents. It is called from the PlayControlsRow. */
public void update() {
updateAll(false);
diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java
index fe52b25..418560a 100644
--- a/src/com/android/tv/menu/TvOptionsRowAdapter.java
+++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java
@@ -19,8 +19,8 @@
import android.content.Context;
import android.media.tv.TvTrackInfo;
import com.android.tv.TvOptionsManager;
+import com.android.tv.common.BuildConfig;
import com.android.tv.common.customization.CustomAction;
-import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.DisplayMode;
import com.android.tv.features.TvFeatures;
import com.android.tv.ui.TvViewUiManager;
@@ -28,6 +28,7 @@
import com.android.tv.ui.sidepanel.DeveloperOptionFragment;
import com.android.tv.ui.sidepanel.DisplayModeFragment;
import com.android.tv.ui.sidepanel.MultiAudioFragment;
+import com.android.tv.common.flags.LegacyFlags;
import java.util.ArrayList;
import java.util.List;
@@ -35,8 +36,12 @@
* An adapter of options.
*/
public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
- public TvOptionsRowAdapter(Context context, List<CustomAction> customActions) {
+ private final LegacyFlags mLegacyFlags;
+
+ public TvOptionsRowAdapter(
+ Context context, List<CustomAction> customActions, LegacyFlags mLegacyFlags) {
super(context, customActions);
+ this.mLegacyFlags = mLegacyFlags;
}
@Override
@@ -49,7 +54,7 @@
}
actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
actionList.add(MenuAction.MORE_CHANNELS_ACTION);
- if (CommonUtils.isDeveloper()) {
+ if (BuildConfig.ENG || mLegacyFlags.enableDeveloperFeatures()) {
actionList.add(MenuAction.DEV_ACTION);
}
actionList.add(MenuAction.SETTINGS_ACTION);
diff --git a/src/com/android/tv/modules/TvApplicationModule.java b/src/com/android/tv/modules/TvApplicationModule.java
index 45383ae..99753d1 100644
--- a/src/com/android/tv/modules/TvApplicationModule.java
+++ b/src/com/android/tv/modules/TvApplicationModule.java
@@ -16,43 +16,101 @@
package com.android.tv.modules;
import android.content.Context;
+
import com.android.tv.MainActivity;
+import com.android.tv.SetupPassthroughActivity;
import com.android.tv.TvApplication;
+import com.android.tv.common.buildtype.BuildTypeModule;
import com.android.tv.common.concurrent.NamedThreadFactory;
import com.android.tv.common.dagger.ApplicationModule;
import com.android.tv.common.dagger.annotations.ApplicationContext;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ChannelDataManagerFactory;
+import com.android.tv.data.epg.EpgFetchService;
+import com.android.tv.data.epg.EpgFetcher;
+import com.android.tv.data.epg.EpgFetcherImpl;
+import com.android.tv.dialog.PinDialogFragment;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.DvrDataManagerImpl;
+import com.android.tv.dvr.WritableDvrDataManager;
+import com.android.tv.dvr.ui.playback.DvrPlaybackActivity;
import com.android.tv.onboarding.OnboardingActivity;
+import com.android.tv.onboarding.SetupSourcesFragment;
+import com.android.tv.setup.SystemSetupActivity;
+import com.android.tv.ui.DetailsActivity;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.TvInputManagerHelper;
+
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.android.ContributesAndroidInjector;
+
+import com.android.tv.common.flags.LegacyFlags;
+
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+
import javax.inject.Singleton;
/** Dagger module for {@link TvApplication}. */
@Module(
includes = {
ApplicationModule.class,
- TvSingletonsModule.class,
+ BuildTypeModule.class,
+ DetailsActivity.Module.class,
+ DvrPlaybackActivity.Module.class,
MainActivity.Module.class,
- OnboardingActivity.Module.class
+ OnboardingActivity.Module.class,
+ SetupPassthroughActivity.Module.class,
+ SetupSourcesFragment.ContentFragment.Module.class,
+ SystemSetupActivity.Module.class,
+ TvSingletonsModule.class,
})
-public class TvApplicationModule {
+public abstract class TvApplicationModule {
private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory("tv-app-db");
@Provides
@AsyncDbTask.DbExecutor
@Singleton
- Executor providesDbExecutor() {
+ static Executor providesDbExecutor() {
return Executors.newSingleThreadExecutor(THREAD_FACTORY);
}
@Provides
@Singleton
- TvInputManagerHelper providesTvInputManagerHelper(@ApplicationContext Context context) {
- TvInputManagerHelper tvInputManagerHelper = new TvInputManagerHelper(context);
+ static TvInputManagerHelper providesTvInputManagerHelper(
+ @ApplicationContext Context context, LegacyFlags legacyFlags) {
+ TvInputManagerHelper tvInputManagerHelper = new TvInputManagerHelper(context, legacyFlags);
tvInputManagerHelper.start();
+ // Since this is injected as a Lazy in the application start is delayed.
return tvInputManagerHelper;
}
+
+ @Provides
+ @Singleton
+ static ChannelDataManager providesChannelDataManager(ChannelDataManagerFactory factory) {
+ ChannelDataManager channelDataManager = factory.create();
+ channelDataManager.start();
+ // Since this is injected as a Lazy in the application start is delayed.
+ return channelDataManager;
+ }
+
+ @Binds
+ @Singleton
+ abstract DvrDataManager providesDvrDataManager(DvrDataManagerImpl impl);
+
+ @Binds
+ @Singleton
+ abstract WritableDvrDataManager providesWritableDvrDataManager(DvrDataManagerImpl impl);
+
+ @Binds
+ @Singleton
+ abstract EpgFetcher epgFetcher(EpgFetcherImpl impl);
+
+ @ContributesAndroidInjector
+ abstract PinDialogFragment contributesPinDialogFragment();
+
+ @ContributesAndroidInjector
+ abstract EpgFetchService contributesEpgFetchService();
}
diff --git a/src/com/android/tv/modules/TvSingletonsModule.java b/src/com/android/tv/modules/TvSingletonsModule.java
index f998c08..f8d10fd 100644
--- a/src/com/android/tv/modules/TvSingletonsModule.java
+++ b/src/com/android/tv/modules/TvSingletonsModule.java
@@ -16,8 +16,9 @@
package com.android.tv.modules;
import com.android.tv.TvSingletons;
-import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.dvr.DvrWatchedPositionManager;
+
import dagger.Module;
import dagger.Provides;
@@ -36,8 +37,8 @@
}
@Provides
- ChannelDataManager providesChannelDataManager() {
- return mTvSingletons.getChannelDataManager();
+ DvrWatchedPositionManager providesDvrWatchedPositionManager() {
+ return mTvSingletons.getDvrWatchedPositionManager();
}
@Provides
diff --git a/src/com/android/tv/onboarding/OnboardingActivity.java b/src/com/android/tv/onboarding/OnboardingActivity.java
index 776ae66..1739e5a 100644
--- a/src/com/android/tv/onboarding/OnboardingActivity.java
+++ b/src/com/android/tv/onboarding/OnboardingActivity.java
@@ -88,7 +88,7 @@
TvSingletons singletons = TvSingletons.getSingletons(this);
mInputManager = singletons.getTvInputManagerHelper();
if (PermissionUtils.hasAccessAllEpg(this) || PermissionUtils.hasReadTvListings(this)) {
- // Make the channels of the new inputs which have been setup outside Live TV
+ // Make the channels of the new inputs which have been setup outside TV app
// browsable.
if (mChannelDataManager.isDbLoadFinished()) {
mSetupUtils.markNewChannelsBrowsable();
@@ -187,7 +187,7 @@
}
// Even though other app can handle the intent, the setup launched by
// Live
- // channels should go through Live channels SetupPassthroughActivity.
+ // channels should go through TV app SetupPassthroughActivity.
intent.setComponent(
new ComponentName(this, SetupPassthroughActivity.class));
try {
diff --git a/src/com/android/tv/onboarding/SetupSourcesFragment.java b/src/com/android/tv/onboarding/SetupSourcesFragment.java
index 3566c9c..2b0a736 100644
--- a/src/com/android/tv/onboarding/SetupSourcesFragment.java
+++ b/src/com/android/tv/onboarding/SetupSourcesFragment.java
@@ -16,33 +16,44 @@
package com.android.tv.onboarding;
-import android.content.Context;
+import android.app.Activity;
import android.graphics.Typeface;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.VerticalGridView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidedActionsStylist;
+import androidx.leanback.widget.VerticalGridView;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.TvInputNewComparator;
+import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
import com.android.tv.ui.GuidedActionsStylistWithDivider;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
+
+import com.google.common.base.Optional;
+
+import dagger.android.AndroidInjection;
+import dagger.android.ContributesAndroidInjector;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import javax.inject.Inject;
+
/** A fragment for channel source info/setup. */
public class SetupSourcesFragment extends SetupMultiPaneFragment {
/** The action category for the actions which is fired from this fragment. */
@@ -106,9 +117,10 @@
private static final int PENDING_ACTION_INPUT_CHANGED = 1;
private static final int PENDING_ACTION_CHANNEL_CHANGED = 2;
- private TvInputManagerHelper mInputManager;
- private ChannelDataManager mChannelDataManager;
- private SetupUtils mSetupUtils;
+ @Inject TvInputManagerHelper mInputManager;
+ @Inject ChannelDataManager mChannelDataManager;
+ @Inject SetupUtils mSetupUtils;
+ @Inject Optional<BuiltInTunerManager> mBuiltInTunerManagerOptional;
private List<TvInputInfo> mInputs;
private int mKnownInputStartIndex;
private int mDoneInputStartIndex;
@@ -187,30 +199,31 @@
@Override
public void onCreate(Bundle savedInstanceState) {
- Context context = getActivity();
- TvSingletons singletons = TvSingletons.getSingletons(context);
- mInputManager = singletons.getTvInputManagerHelper();
- mChannelDataManager = singletons.getChannelDataManager();
- mSetupUtils = singletons.getSetupUtils();
+ super.onCreate(savedInstanceState);
+ mParentFragment = (SetupSourcesFragment) getParentFragment();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ AndroidInjection.inject(this);
+ super.onAttach(activity);
buildInputs();
mInputManager.addCallback(mInputCallback);
mChannelDataManager.addListener(mChannelDataManagerListener);
- super.onCreate(savedInstanceState);
mParentFragment = (SetupSourcesFragment) getParentFragment();
- if (singletons.getBuiltInTunerManager().isPresent()) {
- singletons
- .getBuiltInTunerManager()
+ if (mBuiltInTunerManagerOptional.isPresent()) {
+ mBuiltInTunerManagerOptional
.get()
.getTunerInputController()
- .executeNetworkTunerDiscoveryAsyncTask(getContext());
+ .executeNetworkTunerDiscoveryAsyncTask(activity);
}
}
@Override
- public void onDestroy() {
- super.onDestroy();
+ public void onDetach() {
mChannelDataManager.removeListener(mChannelDataManagerListener);
mInputManager.removeCallback(mInputCallback);
+ super.onDetach();
}
@NonNull
@@ -404,5 +417,13 @@
setAccessibilityDelegate(vh, action);
}
}
+ /**
+ * Exports {@link ContentFragment} for Dagger codegen to create the appropriate injector.
+ */
+ @dagger.Module
+ public abstract static class Module {
+ @ContributesAndroidInjector
+ abstract ContentFragment contributesContentFragment();
+ }
}
}
diff --git a/src/com/android/tv/onboarding/WelcomeFragment.java b/src/com/android/tv/onboarding/WelcomeFragment.java
index 8c119a8..667da05 100644
--- a/src/com/android/tv/onboarding/WelcomeFragment.java
+++ b/src/com/android/tv/onboarding/WelcomeFragment.java
@@ -25,7 +25,7 @@
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.app.OnboardingFragment;
+import androidx.leanback.app.OnboardingFragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
@@ -621,9 +621,9 @@
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
setLogoResourceId(R.drawable.splash_logo);
- mTitleView = view.findViewById(android.support.v17.leanback.R.id.title);
- mPagingIndicator = view.findViewById(android.support.v17.leanback.R.id.page_indicator);
- mStartButton = view.findViewById(android.support.v17.leanback.R.id.button_start);
+ mTitleView = view.findViewById(androidx.leanback.R.id.title);
+ mPagingIndicator = view.findViewById(androidx.leanback.R.id.page_indicator);
+ mStartButton = view.findViewById(androidx.leanback.R.id.button_start);
mStartButton.setAccessibilityDelegate(
new AccessibilityDelegate() {
diff --git a/src/com/android/tv/parental/ContentRatingsManager.java b/src/com/android/tv/parental/ContentRatingsManager.java
index 32a1325..174039b 100644
--- a/src/com/android/tv/parental/ContentRatingsManager.java
+++ b/src/com/android/tv/parental/ContentRatingsManager.java
@@ -22,6 +22,7 @@
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.android.tv.R;
+import com.android.tv.common.util.PermissionUtils;
import com.android.tv.parental.ContentRatingSystem.Rating;
import com.android.tv.parental.ContentRatingSystem.SubRating;
import com.android.tv.util.TvInputManagerHelper;
@@ -42,13 +43,14 @@
public void update() {
mContentRatingSystems.clear();
- ContentRatingsParser parser = new ContentRatingsParser(mContext);
-
- List<TvContentRatingSystemInfo> infos = mTvInputManager.getTvContentRatingSystemList();
- for (TvContentRatingSystemInfo info : infos) {
- List<ContentRatingSystem> list = parser.parse(info);
- if (list != null) {
- mContentRatingSystems.addAll(list);
+ if (PermissionUtils.hasReadContetnRatingSystem(mContext)) {
+ ContentRatingsParser parser = new ContentRatingsParser(mContext);
+ List<TvContentRatingSystemInfo> infos = mTvInputManager.getTvContentRatingSystemList();
+ for (TvContentRatingSystemInfo info : infos) {
+ List<ContentRatingSystem> list = parser.parse(info);
+ if (list != null) {
+ mContentRatingSystems.addAll(list);
+ }
}
}
}
diff --git a/src/com/android/tv/parental/ParentalControlSettings.java b/src/com/android/tv/parental/ParentalControlSettings.java
index b41b160..9990ae3 100644
--- a/src/com/android/tv/parental/ParentalControlSettings.java
+++ b/src/com/android/tv/parental/ParentalControlSettings.java
@@ -19,12 +19,12 @@
import android.content.Context;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputManager;
-import com.android.tv.common.experiments.Experiments;
import com.android.tv.parental.ContentRatingSystem.Rating;
import com.android.tv.parental.ContentRatingSystem.SubRating;
import com.android.tv.util.TvSettings;
import com.android.tv.util.TvSettings.ContentRatingLevel;
import com.google.common.collect.ImmutableList;
+import com.android.tv.common.flags.LegacyFlags;
import java.util.HashSet;
import java.util.Set;
@@ -40,14 +40,16 @@
private final Context mContext;
private final TvInputManager mTvInputManager;
+ private final LegacyFlags mLegacyFlags;
// mRatings is expected to be synchronized with mTvInputManager.getBlockedRatings().
private Set<TvContentRating> mRatings;
private Set<TvContentRating> mCustomRatings;
- public ParentalControlSettings(Context context) {
+ public ParentalControlSettings(Context context, LegacyFlags legacyFlags) {
mContext = context;
mTvInputManager = (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE);
+ mLegacyFlags = legacyFlags;
}
public boolean isParentalControlsEnabled() {
@@ -130,7 +132,7 @@
} else {
mRatings = ContentRatingLevelPolicy.getRatingsForLevel(this, manager, level);
if (level != TvSettings.CONTENT_RATING_LEVEL_NONE
- && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) {
+ && mLegacyFlags.enableUnratedContentSettings()) {
// UNRATED contents should be blocked unless the rating level is none or custom
mRatings.add(TvContentRating.UNRATED);
}
diff --git a/src/com/android/tv/perf/PerformanceMonitor.java b/src/com/android/tv/perf/PerformanceMonitor.java
index b1ae759..30197c7 100644
--- a/src/com/android/tv/perf/PerformanceMonitor.java
+++ b/src/com/android/tv/perf/PerformanceMonitor.java
@@ -96,4 +96,14 @@
* @return true if the activity is available to start
*/
boolean startPerformanceMonitorEventDebugActivity(Context context);
+
+ /**
+ * Initialize crash monitoring for an app by wrapping the default {@link
+ * Thread.UncaughtExceptionHandler} with a handler that can report crashes to the performance
+ * montitor and then delegate the handling of the UncaughtException to the original default
+ * {@link Thread.UncaughtExceptionHandler}.
+ *
+ * <p>Note: This will override the current {@link Thread.UncaughtExceptionHandler}.
+ */
+ void startCrashMonitor();
}
diff --git a/src/com/android/tv/perf/PerformanceMonitorManager.java b/src/com/android/tv/perf/PerformanceMonitorManager.java
deleted file mode 100644
index db6667d..0000000
--- a/src/com/android/tv/perf/PerformanceMonitorManager.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2018 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.perf;
-
-import android.app.Application;
-
-/** Manages the initialization of Performance Monitoring. */
-public interface PerformanceMonitorManager {
-
- /**
- * Initializes the {@link com.android.tv.perf.PerformanceMonitor}.
- *
- * <p>This should only be called once.
- */
- PerformanceMonitor initialize(Application app);
-
- /**
- * Returns a lightweight object to help measure both cold and warm startup latency.
- *
- * <p>This method is idempotent and lightweight. It can be called multiple times and does not
- * need to be cached.
- */
- StartupMeasure getStartupMeasure();
-}
diff --git a/src/com/android/tv/perf/PerformanceMonitorManagerFactory.java b/src/com/android/tv/perf/PerformanceMonitorManagerFactory.java
deleted file mode 100644
index fe3ea14..0000000
--- a/src/com/android/tv/perf/PerformanceMonitorManagerFactory.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2018 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.perf;
-
-import com.android.tv.perf.stub.StubPerformanceMonitorManager;
-import javax.inject.Inject;
-
-public final class PerformanceMonitorManagerFactory {
- private static final PerformanceMonitorManagerFactory INSTANCE =
- new PerformanceMonitorManagerFactory();
-
- @Inject
- public PerformanceMonitorManagerFactory() {}
-
- public static PerformanceMonitorManager create() {
- return INSTANCE.get();
- }
-
- public PerformanceMonitorManager get() {
- return new StubPerformanceMonitorManager();
- }
-}
diff --git a/src/com/android/tv/perf/StartupMeasure.java b/src/com/android/tv/perf/StartupMeasure.java
index 5cf183c..c7fa50f 100644
--- a/src/com/android/tv/perf/StartupMeasure.java
+++ b/src/com/android/tv/perf/StartupMeasure.java
@@ -19,8 +19,16 @@
import android.app.Application;
/**
- * Measures App startup. This interface is lightweight to help measure both cold and warm startup
- * latency. Implementations must not throw any Exception.
+ * Measures App startup.
+ *
+ * <p>This interface is lightweight to help measure both cold and warm startup latency.
+ * Implementations must not throw any Exception.
+ *
+ * <p>Because this class needs to be used in static initialization blocks, it can not be injected
+ * via dagger.
+ *
+ * <p>Creating implementations of this interface must be idempotent and lightweight. It does not
+ * need to be cached.
*/
public interface StartupMeasure {
diff --git a/src/com/android/tv/perf/StartupMeasureFactory.java b/src/com/android/tv/perf/StartupMeasureFactory.java
new file mode 100644
index 0000000..a99c88a
--- /dev/null
+++ b/src/com/android/tv/perf/StartupMeasureFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 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.perf;
+
+
+import com.android.tv.perf.stub.StubStartupMeasure;
+
+import com.google.common.base.Supplier;
+import java.lang.Override;
+import javax.inject.Inject;
+
+/** Factory for {@link StartupMeasure}.
+ *
+ * <p>Hardcoded to {@link StubStartupMeasure}.
+ */
+public final class StartupMeasureFactory implements Supplier<StartupMeasure> {
+ private static final StartupMeasureFactory INSTANCE = new StartupMeasureFactory();
+
+ @Inject
+ public StartupMeasureFactory() {}
+
+ public static StartupMeasure create() {
+ return INSTANCE.get();
+ }
+
+ @Override
+ public StartupMeasure get() {
+ return new StubStartupMeasure();
+ }
+}
diff --git a/src/com/android/tv/perf/stub/StubPerformanceMonitor.java b/src/com/android/tv/perf/stub/StubPerformanceMonitor.java
index 80c2f6c..ac3dc25 100644
--- a/src/com/android/tv/perf/stub/StubPerformanceMonitor.java
+++ b/src/com/android/tv/perf/stub/StubPerformanceMonitor.java
@@ -56,7 +56,6 @@
return false;
}
- public static TimerEvent startBootstrapTimer() {
- return new TimerEvent() {};
- }
+ @Override
+ public void startCrashMonitor() {}
}
diff --git a/src/com/android/tv/perf/stub/StubPerformanceMonitorManager.java b/src/com/android/tv/perf/stub/StubPerformanceMonitorManager.java
deleted file mode 100644
index 0c26815..0000000
--- a/src/com/android/tv/perf/stub/StubPerformanceMonitorManager.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 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.perf.stub;
-
-import android.app.Application;
-import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.perf.PerformanceMonitorManager;
-import com.android.tv.perf.StartupMeasure;
-
-/** Manages a stub implementation of Performance Monitoring. */
-public class StubPerformanceMonitorManager implements PerformanceMonitorManager {
-
- @Override
- public PerformanceMonitor initialize(Application app) {
- return new StubPerformanceMonitor();
- }
-
- @Override
- public StartupMeasure getStartupMeasure() {
- return new StubStartupMeasure();
- }
-}
diff --git a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
index 3fb6624..5fa7606 100644
--- a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
+++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
@@ -42,7 +42,7 @@
// AC3 capabilities stat is sent to Google Analytics just once in order to avoid
// duplicated stat reports since it doesn't change over time in most cases.
// Increase this revision when we should force the stat to be sent again.
- // TODO: Consier using custom metrics.
+ // TODO: Consider using custom metrics.
private static final int REPORT_REVISION = 1;
private final Context mContext;
diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java
index 5bc6d72..7ff67b5 100644
--- a/src/com/android/tv/receiver/PackageIntentsReceiver.java
+++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java
@@ -23,9 +23,7 @@
import android.util.Log;
import com.android.tv.Starter;
import com.android.tv.TvSingletons;
-import com.android.tv.features.TvFeatures;
import com.android.tv.util.Partner;
-import com.google.android.tv.partner.support.EpgContract;
/** A class for handling the broadcast intents from PackageManager. */
public class PackageIntentsReceiver extends BroadcastReceiver {
diff --git a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java
index 2590a33..61ebb2d 100644
--- a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java
+++ b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java
@@ -27,15 +27,18 @@
import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;
+
import androidx.tvprovider.media.tv.TvContractCompat;
+
import com.android.tv.Starter;
import com.android.tv.TvSingletons;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.PreviewProgramContent;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.util.Utils;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
diff --git a/src/com/android/tv/recommendation/ChannelRecord.java b/src/com/android/tv/recommendation/ChannelRecord.java
index c7a7cb3..f047aac 100644
--- a/src/com/android/tv/recommendation/ChannelRecord.java
+++ b/src/com/android/tv/recommendation/ChannelRecord.java
@@ -19,10 +19,12 @@
import android.content.Context;
import android.support.annotation.GuardedBy;
import android.support.annotation.VisibleForTesting;
+
import com.android.tv.TvSingletons;
-import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+
import java.util.ArrayDeque;
import java.util.Deque;
diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java
index f40a086..1652bd7 100644
--- a/src/com/android/tv/recommendation/NotificationService.java
+++ b/src/com/android/tv/recommendation/NotificationService.java
@@ -40,19 +40,21 @@
import android.util.Log;
import android.util.SparseLongArray;
import android.view.View;
+
import com.android.tv.MainActivityWrapper.OnCurrentChannelChangeListener;
import com.android.tv.R;
import com.android.tv.Starter;
import com.android.tv.TvSingletons;
import com.android.tv.common.CommonConstants;
import com.android.tv.common.WeakHandler;
-import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
import com.android.tv.util.images.BitmapUtils;
import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo;
import com.android.tv.util.images.ImageLoader;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java
index fc20031..e254ba5 100644
--- a/src/com/android/tv/recommendation/RecommendationDataManager.java
+++ b/src/com/android/tv/recommendation/RecommendationDataManager.java
@@ -34,14 +34,17 @@
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.WeakHandler;
import com.android.tv.common.util.PermissionUtils;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.WatchedHistoryManager;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.util.TvUriMatcher;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -232,6 +235,9 @@
@MainThread
private void stop() {
+ if (mWatchedHistoryManager != null) {
+ mWatchedHistoryManager.setListener(null);
+ }
for (int what = MSG_FIRST; what <= MSG_LAST; ++what) {
mHandler.removeMessages(what);
}
@@ -359,7 +365,7 @@
WatchedHistoryManager.WatchedRecord watchedRecord) {
long endTime = watchedRecord.watchedStartTime + watchedRecord.duration;
Program program =
- new Program.Builder()
+ new ProgramImpl.Builder()
.setChannelId(watchedRecord.channelId)
.setTitle("")
.setStartTimeUtcMillis(watchedRecord.watchedStartTime)
@@ -411,7 +417,7 @@
}
Program program =
- new Program.Builder()
+ new ProgramImpl.Builder()
.setChannelId(cursor.getLong(mIndexWatchChannelId))
.setTitle(cursor.getString(mIndexProgramTitle))
.setStartTimeUtcMillis(cursor.getLong(mIndexProgramStartTime))
diff --git a/src/com/android/tv/recommendation/Recommender.java b/src/com/android/tv/recommendation/Recommender.java
index f350799..a8535a4 100644
--- a/src/com/android/tv/recommendation/Recommender.java
+++ b/src/com/android/tv/recommendation/Recommender.java
@@ -20,7 +20,9 @@
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Pair;
+
import com.android.tv.data.api.Channel;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -127,7 +129,7 @@
}
}
if (!mIncludeRecommendedOnly || maxScore != Evaluator.NOT_RECOMMENDED) {
- records.add(new Pair<>(cr.getChannel(), maxScore));
+ records.add(Pair.create(cr.getChannel(), maxScore));
}
}
if (size > records.size()) {
diff --git a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java
index 9240682..b3952c0 100644
--- a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java
+++ b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java
@@ -19,7 +19,9 @@
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
-import com.android.tv.data.Program;
+
+import com.android.tv.data.api.Program;
+
import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Calendar;
diff --git a/src/com/android/tv/recommendation/WatchedProgram.java b/src/com/android/tv/recommendation/WatchedProgram.java
index 239de1f..0da9c62 100644
--- a/src/com/android/tv/recommendation/WatchedProgram.java
+++ b/src/com/android/tv/recommendation/WatchedProgram.java
@@ -16,7 +16,7 @@
package com.android.tv.recommendation;
-import com.android.tv.data.Program;
+import com.android.tv.data.api.Program;
public final class WatchedProgram {
private final Program mProgram;
diff --git a/src/com/android/tv/search/DataManagerSearch.java b/src/com/android/tv/search/DataManagerSearch.java
index a649c0a..1a0ada3 100644
--- a/src/com/android/tv/search/DataManagerSearch.java
+++ b/src/com/android/tv/search/DataManagerSearch.java
@@ -26,15 +26,18 @@
import android.support.annotation.MainThread;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.search.LocalSearchProvider.SearchResult;
import com.android.tv.util.MainThreadExecutor;
import com.android.tv.util.Utils;
+
import com.google.common.collect.ImmutableList;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
diff --git a/src/com/android/tv/search/LocalSearchProvider.java b/src/com/android/tv/search/LocalSearchProvider.java
index 5652c98..8699956 100644
--- a/src/com/android/tv/search/LocalSearchProvider.java
+++ b/src/com/android/tv/search/LocalSearchProvider.java
@@ -17,7 +17,6 @@
package com.android.tv.search;
import android.app.SearchManager;
-import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
@@ -28,21 +27,30 @@
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.TvSingletons;
+
import com.android.tv.common.CommonConstants;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.dagger.init.SafePreDaggerInitializer;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.common.util.PermissionUtils;
import com.android.tv.perf.EventNames;
import com.android.tv.perf.PerformanceMonitor;
import com.android.tv.perf.TimerEvent;
import com.android.tv.util.TvUriMatcher;
+
import com.google.auto.value.AutoValue;
+
+import dagger.android.ContributesAndroidInjector;
+import dagger.android.DaggerContentProvider;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-public class LocalSearchProvider extends ContentProvider {
+import javax.inject.Inject;
+
+/** Content provider for local search */
+public class LocalSearchProvider extends DaggerContentProvider {
private static final String TAG = "LocalSearchProvider";
private static final boolean DEBUG = false;
@@ -79,14 +87,18 @@
private static final String NO_LIVE_CONTENTS = "0";
private static final String LIVE_CONTENTS = "1";
- private PerformanceMonitor mPerformanceMonitor;
+ @Inject PerformanceMonitor mPerformanceMonitor;
/** Used only for testing */
private SearchInterface mSearchInterface;
@Override
public boolean onCreate() {
- mPerformanceMonitor = TvSingletons.getSingletons(getContext()).getPerformanceMonitor();
+ SafePreDaggerInitializer.init(getContext());
+ if (!super.onCreate()) {
+ Log.e(TAG, "LocalSearchProvider.onCreate() failed.");
+ return false;
+ }
return true;
}
@@ -221,6 +233,13 @@
throw new UnsupportedOperationException();
}
+ /** Module for {@link LocalSearchProvider} */
+ @dagger.Module
+ public abstract static class Module {
+ @ContributesAndroidInjector
+ abstract LocalSearchProvider contributesLocalSearchProviderInjector();
+ }
+
/** A placeholder to a search result. */
@AutoValue
public abstract static class SearchResult {
@@ -235,6 +254,8 @@
.setProgressPercentage(0);
}
+ public abstract Builder toBuilder();
+
@AutoValue.Builder
abstract static class Builder {
abstract Builder setChannelId(long value);
diff --git a/src/com/android/tv/search/ProgramGuideSearchFragment.java b/src/com/android/tv/search/ProgramGuideSearchFragment.java
index 6c94bd3..fa2e451 100644
--- a/src/com/android/tv/search/ProgramGuideSearchFragment.java
+++ b/src/com/android/tv/search/ProgramGuideSearchFragment.java
@@ -21,18 +21,18 @@
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.support.v17.leanback.app.SearchFragment;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SearchBar;
+import androidx.leanback.app.SearchFragment;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.HeaderItem;
+import androidx.leanback.widget.ImageCardView;
+import androidx.leanback.widget.ListRow;
+import androidx.leanback.widget.ListRowPresenter;
+import androidx.leanback.widget.ObjectAdapter;
+import androidx.leanback.widget.OnItemViewClickedListener;
+import androidx.leanback.widget.Presenter;
+import androidx.leanback.widget.Row;
+import androidx.leanback.widget.RowPresenter;
+import androidx.leanback.widget.SearchBar;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
diff --git a/src/com/android/tv/search/TvProviderSearch.java b/src/com/android/tv/search/TvProviderSearch.java
index 8a1f51f..c46938a 100644
--- a/src/com/android/tv/search/TvProviderSearch.java
+++ b/src/com/android/tv/search/TvProviderSearch.java
@@ -308,7 +308,7 @@
if (c != null && c.moveToNext() && !isRatingBlocked(c.getString(2))) {
String channelName = result.getTitle();
String channelNumber = result.getChannelNumber();
- SearchResult.Builder builder = SearchResult.builder();
+ SearchResult.Builder builder = result.toBuilder();
long startUtcMillis = c.getLong(5);
long endUtcMillis = c.getLong(6);
builder.setTitle(c.getString(0));
diff --git a/src/com/android/tv/setup/SystemSetupActivity.java b/src/com/android/tv/setup/SystemSetupActivity.java
index b2160b3..999b157 100644
--- a/src/com/android/tv/setup/SystemSetupActivity.java
+++ b/src/com/android/tv/setup/SystemSetupActivity.java
@@ -24,9 +24,9 @@
import android.media.tv.TvInputInfo;
import android.os.Bundle;
import android.widget.Toast;
+
import com.android.tv.R;
import com.android.tv.SetupPassthroughActivity;
-import com.android.tv.TvSingletons;
import com.android.tv.common.CommonConstants;
import com.android.tv.common.ui.setup.SetupActivity;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
@@ -36,6 +36,11 @@
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
+import dagger.android.AndroidInjection;
+import dagger.android.ContributesAndroidInjector;
+
+import javax.inject.Inject;
+
/** A activity to start input sources setup fragment for initial setup flow. */
public class SystemSetupActivity extends SetupActivity {
private static final String SYSTEM_SETUP =
@@ -43,18 +48,17 @@
private static final int SHOW_RIPPLE_DURATION_MS = 266;
private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
- private TvInputManagerHelper mInputManager;
+ @Inject TvInputManagerHelper mInputManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
+ AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (!SYSTEM_SETUP.equals(intent.getAction())) {
finish();
return;
}
- TvSingletons singletons = TvSingletons.getSingletons(this);
- mInputManager = singletons.getTvInputManagerHelper();
}
@Override
@@ -92,7 +96,7 @@
}
// Even though other app can handle the intent, the setup launched by
// Live
- // channels should go through Live channels SetupPassthroughActivity.
+ // channels should go through TV app SetupPassthroughActivity.
intent.setComponent(
new ComponentName(this, SetupPassthroughActivity.class));
try {
@@ -124,4 +128,13 @@
}
return false;
}
+
+ /**
+ * Exports {@link SystemSetupActivity} for Dagger codegen to create the appropriate injector.
+ */
+ @dagger.Module
+ public abstract static class Module {
+ @ContributesAndroidInjector
+ abstract SystemSetupActivity contributeSystemSetupActivity();
+ }
}
diff --git a/src/com/android/tv/ui/AppLayerTvView.java b/src/com/android/tv/ui/AppLayerTvView.java
index e2b64a1..4c54fb3 100644
--- a/src/com/android/tv/ui/AppLayerTvView.java
+++ b/src/com/android/tv/ui/AppLayerTvView.java
@@ -21,7 +21,6 @@
import android.view.SurfaceView;
import android.view.View;
import com.android.tv.common.compat.TvViewCompat;
-import com.android.tv.common.util.CommonUtils;
import com.android.tv.common.util.Debug;
/**
@@ -29,9 +28,14 @@
*
* <p>Once an app starts using additional window like SubPanel and it gets window focus, the {@link
* android.media.tv.TvView#setMain()} does not work because its implementation assumes that the app
- * uses only application layer. TODO: remove this class once the TvView.setMain() is revisited.
+ * uses only application layer.
+ *
+ * <p>TODO: remove this class once the TvView.setMain() is revisited.
*/
public class AppLayerTvView extends TvViewCompat {
+
+ boolean mUseSecureSurface = true;
+
public AppLayerTvView(Context context) {
super(context);
}
@@ -44,6 +48,11 @@
super(context, attrs, defStyleAttr);
}
+ /** Set the security of children {@link SurfaceView}s to {@code secure} */
+ public void setUseSecureSurface(boolean secure) {
+ mUseSecureSurface = secure;
+ }
+
@Override
public boolean hasWindowFocus() {
return true;
@@ -53,7 +62,7 @@
public void onViewAdded(View child) {
if (child instanceof SurfaceView) {
// Note: See b/29118070 for detail.
- ((SurfaceView) child).setSecure(!CommonUtils.isDeveloper());
+ ((SurfaceView) child).setSecure(mUseSecureSurface);
}
super.onViewAdded(child);
}
diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java
index 00ac7e3..dba9ceb 100644
--- a/src/com/android/tv/ui/ChannelBannerView.java
+++ b/src/com/android/tv/ui/ChannelBannerView.java
@@ -46,13 +46,15 @@
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
+
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.singletons.HasSingletons;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.StreamInfo;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.parental.ContentRatingsManager;
@@ -64,7 +66,9 @@
import com.android.tv.util.images.ImageLoader;
import com.android.tv.util.images.ImageLoader.ImageLoaderCallback;
import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask;
+
import com.google.common.collect.ImmutableList;
+
import javax.inject.Provider;
/** A view to render channel banner. */
@@ -118,6 +122,7 @@
private final TvInputManagerHelper mTvInputManagerHelper;
// TvOverlayManager is always created after ChannelBannerView
private final Provider<TvOverlayManager> mTvOverlayManager;
+ private final AccessibilityManager mAccessibilityManager;
private View mChannelView;
@@ -265,12 +270,12 @@
mContentRatingsManager = mTvInputManagerHelper.getContentRatingsManager();
mNoProgram =
- new Program.Builder()
+ new ProgramImpl.Builder()
.setTitle(context.getString(R.string.channel_banner_no_title))
.setDescription(EMPTY_STRING)
.build();
mLockedChannelProgram =
- new Program.Builder()
+ new ProgramImpl.Builder()
.setTitle(context.getString(R.string.channel_banner_locked_channel_title))
.setDescription(EMPTY_STRING)
.build();
@@ -278,8 +283,7 @@
sClosedCaptionMark = context.getString(R.string.closed_caption);
}
mAutoHideScheduler = new AutoHideScheduler(context, this::hide);
- context.getSystemService(AccessibilityManager.class)
- .addAccessibilityStateChangeListener(mAutoHideScheduler);
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
}
@Override
@@ -319,6 +323,18 @@
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAccessibilityManager.addAccessibilityStateChangeListener(mAutoHideScheduler);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mAccessibilityManager.removeAccessibilityStateChangeListener(mAutoHideScheduler);
+ super.onDetachedFromWindow();
+ }
+
+ @Override
public void onEnterAction(boolean fromEmptyScene) {
resetAnimationEffects();
if (fromEmptyScene) {
@@ -735,15 +751,23 @@
} else {
ImmutableList<TvContentRating> ratings =
(program == null) ? null : program.getContentRatings();
- for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
- if (ratings == null || ratings.size() <= i) {
- mContentRatingsTextViews[i].setVisibility(View.GONE);
- } else {
- mContentRatingsTextViews[i].setText(
- mContentRatingsManager.getDisplayNameForRating(ratings.get(i)));
- mContentRatingsTextViews[i].setVisibility(View.VISIBLE);
+ int ratingsViewIndex = 0;
+ if (ratings != null) {
+ for (int i = 0; i < ratings.size(); i++) {
+ if (ratingsViewIndex < DISPLAYED_CONTENT_RATINGS_COUNT
+ && !TextUtils.isEmpty(
+ mContentRatingsManager.getDisplayNameForRating(
+ ratings.get(i)))) {
+ mContentRatingsTextViews[ratingsViewIndex].setText(
+ mContentRatingsManager.getDisplayNameForRating(ratings.get(i)));
+ mContentRatingsTextViews[ratingsViewIndex].setVisibility(View.VISIBLE);
+ ratingsViewIndex++;
+ }
}
}
+ while (ratingsViewIndex < DISPLAYED_CONTENT_RATINGS_COUNT) {
+ mContentRatingsTextViews[ratingsViewIndex++].setVisibility(View.GONE);
+ }
}
}
diff --git a/src/com/android/tv/ui/DetailsActivity.java b/src/com/android/tv/ui/DetailsActivity.java
index 80c0f64..92c13f5 100644
--- a/src/com/android/tv/ui/DetailsActivity.java
+++ b/src/com/android/tv/ui/DetailsActivity.java
@@ -16,12 +16,11 @@
package com.android.tv.ui;
-import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.DetailsFragment;
+import androidx.leanback.app.DetailsFragment;
import android.transition.Transition;
import android.transition.Transition.TransitionListener;
import android.util.Log;
@@ -35,9 +34,12 @@
import com.android.tv.dvr.ui.browse.RecordedProgramDetailsFragment;
import com.android.tv.dvr.ui.browse.ScheduledRecordingDetailsFragment;
import com.android.tv.dvr.ui.browse.SeriesRecordingDetailsFragment;
+import dagger.android.ContributesAndroidInjector;
+import dagger.android.DaggerActivity;
/** Activity to show details view. */
-public class DetailsActivity extends Activity implements PinDialogFragment.OnPinCheckedListener {
+public class DetailsActivity extends DaggerActivity
+ implements PinDialogFragment.OnPinCheckedListener {
private static final String TAG = "DetailsActivity";
private static final long INVALID_RECORD_ID = -1;
@@ -206,4 +208,15 @@
}
finish();
}
+
+ /** Exports {@link DaggerActivity} for Dagger codegen to create the appropriate injector. */
+ @dagger.Module
+ public abstract static class Module {
+ @ContributesAndroidInjector
+ abstract DetailsActivity contributesDetailsActivityInjector();
+
+ @ContributesAndroidInjector
+ abstract CurrentRecordingDetailsFragment
+ contributesCurrentRecordingDetailsFragmentInjector();
+ }
}
diff --git a/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java b/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java
index 9685b04..3aba5d1 100644
--- a/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java
+++ b/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java
@@ -17,9 +17,9 @@
package com.android.tv.ui;
import android.content.Context;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
+import androidx.leanback.app.GuidedStepFragment;
+import androidx.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidedActionsStylist;
import com.android.tv.R;
/** Extended stylist class used for {@link GuidedStepFragment} with divider support. */
diff --git a/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java b/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java
index 9b916af..703dc24 100644
--- a/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java
+++ b/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java
@@ -17,7 +17,7 @@
import android.os.Message;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.VerticalGridView;
+import androidx.leanback.widget.VerticalGridView;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
diff --git a/src/com/android/tv/ui/ProgramDetailsFragment.java b/src/com/android/tv/ui/ProgramDetailsFragment.java
index 88a7b2c..bfcebd7 100644
--- a/src/com/android/tv/ui/ProgramDetailsFragment.java
+++ b/src/com/android/tv/ui/ProgramDetailsFragment.java
@@ -23,21 +23,23 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
-import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.support.v17.leanback.widget.VerticalGridView;
import android.text.TextUtils;
+
+import androidx.leanback.app.DetailsFragment;
+import androidx.leanback.widget.Action;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.DetailsOverviewRow;
+import androidx.leanback.widget.DetailsOverviewRowPresenter;
+import androidx.leanback.widget.OnActionClickedListener;
+import androidx.leanback.widget.PresenterSelector;
+import androidx.leanback.widget.SparseArrayObjectAdapter;
+import androidx.leanback.widget.VerticalGridView;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
@@ -64,7 +66,7 @@
protected DetailsViewBackgroundHelper mBackgroundHelper;
private ArrayObjectAdapter mRowsAdapter;
private DetailsOverviewRow mDetailsOverview;
- private Program mProgram;
+ private ProgramImpl mProgram;
private String mInputId;
private ScheduledRecording mScheduledRecording;
private DvrManager mDvrManager;
@@ -137,7 +139,7 @@
* the detail activity and fragment will be ended.
*/
private boolean onLoadDetails(Bundle args) {
- Program program = args.getParcelable(DetailsActivity.PROGRAM);
+ ProgramImpl program = args.getParcelable(DetailsActivity.PROGRAM);
long channelId = args.getLong(DetailsActivity.CHANNEL_ID);
String inputId = args.getString(DetailsActivity.INPUT_ID);
if (program != null && channelId != Channel.INVALID_ID && !TextUtils.isEmpty(inputId)) {
diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java
index f4949f0..a0cfad3 100644
--- a/src/com/android/tv/ui/SelectInputView.java
+++ b/src/com/android/tv/ui/SelectInputView.java
@@ -22,8 +22,8 @@
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
+import androidx.leanback.widget.VerticalGridView;
+import androidx.recyclerview.widget.RecyclerView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java
index 5ac6bd8..49f7d4c 100644
--- a/src/com/android/tv/ui/TunableTvView.java
+++ b/src/com/android/tv/ui/TunableTvView.java
@@ -54,11 +54,13 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
+
import com.android.tv.InputSessionManager;
import com.android.tv.InputSessionManager.TvViewSession;
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
+import com.android.tv.common.BuildConfig;
import com.android.tv.common.CommonConstants;
import com.android.tv.common.compat.TvInputConstantCompat;
import com.android.tv.common.compat.TvViewCompat.TvInputCallbackCompat;
@@ -67,11 +69,11 @@
import com.android.tv.common.util.Debug;
import com.android.tv.common.util.DurationTimer;
import com.android.tv.common.util.PermissionUtils;
-import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.StreamInfo;
import com.android.tv.data.WatchedHistoryManager;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.features.TvFeatures;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.parental.ParentalControlSettings;
@@ -81,6 +83,9 @@
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
import com.android.tv.util.images.ImageLoader;
+
+import com.android.tv.common.flags.LegacyFlags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -317,7 +322,7 @@
if (DEBUG) Log.d(TAG, "onVideoAvailable: {inputId=" + inputId + "}");
Debug.getTimer(Debug.TAG_START_UP_TIMER)
.log(
- "Start up of Live TV ends,"
+ "Start up of TV app ends,"
+ " TunableTvView.onVideoAvailable resets timer");
Debug.getTimer(Debug.TAG_START_UP_TIMER).reset();
Debug.removeTimer(Debug.TAG_START_UP_TIMER);
@@ -473,8 +478,12 @@
}
public void initialize(
- ProgramDataManager programDataManager, TvInputManagerHelper tvInputManagerHelper) {
+ ProgramDataManager programDataManager,
+ TvInputManagerHelper tvInputManagerHelper,
+ LegacyFlags mLegacyFlags) {
mTvView = findViewById(R.id.tv_view);
+ mTvView.setUseSecureSurface(!BuildConfig.ENG && !mLegacyFlags.enableDeveloperFeatures());
+
mProgramDataManager = programDataManager;
mInputManagerHelper = tvInputManagerHelper;
mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager();
@@ -553,7 +562,9 @@
}
public void setMain() {
- mTvView.setMain();
+ if (PermissionUtils.hasChangeHdmiCecActiveSource(getContext())) {
+ mTvView.setMain();
+ }
}
public void setWatchedHistoryManager(WatchedHistoryManager watchedHistoryManager) {
@@ -653,17 +664,22 @@
// To reduce the IPCs, unregister the callback here and register it when necessary.
mTvView.setTimeShiftPositionCallback(null);
setTimeShiftAvailable(false);
- if (needSurfaceSizeUpdate && mFixedSurfaceWidth > 0 && mFixedSurfaceHeight > 0) {
- // When the input is changed, TvView recreates its SurfaceView internally.
- // So we need to call SurfaceHolder.setFixedSize for the new SurfaceView.
- getSurfaceView().getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight);
- }
mVideoUnavailableReason = TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING;
if (mTvViewSession != null) {
mTvViewSession.tune(channel, params, listener);
} else {
mTvView.tune(mInputInfo.getId(), mCurrentChannel.getUri(), params);
}
+ if (needSurfaceSizeUpdate && mFixedSurfaceWidth > 0 && mFixedSurfaceHeight > 0) {
+ // When the input is changed, TvView recreates its SurfaceView internally.
+ // So we need to call SurfaceHolder.setFixedSize for the new SurfaceView.
+ SurfaceView surfaceView = getSurfaceView();
+ if (surfaceView != null) {
+ surfaceView.getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight);
+ } else {
+ Log.w(TAG, "Failed to set fixed size for surface view: Null surface view");
+ }
+ }
updateBlockScreenAndMuting();
if (mOnTuneListener != null) {
mOnTuneListener.onStreamInfoChanged(this, true);
diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java
index b2854a1..0ab7c68 100644
--- a/src/com/android/tv/ui/TvOverlayManager.java
+++ b/src/com/android/tv/ui/TvOverlayManager.java
@@ -33,6 +33,7 @@
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+
import com.android.tv.ChannelTuner;
import com.android.tv.MainActivity;
import com.android.tv.MainActivity.KeyHandlerResultType;
@@ -47,6 +48,7 @@
import com.android.tv.common.ui.setup.SetupFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ProgramDataManager;
import com.android.tv.dialog.DvrHistoryDialogFragment;
import com.android.tv.dialog.FullscreenDialogFragment;
import com.android.tv.dialog.HalfSizedDialogFragment;
@@ -68,6 +70,12 @@
import com.android.tv.ui.sidepanel.SideFragmentManager;
import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment;
import com.android.tv.util.TvInputManagerHelper;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
+import com.android.tv.common.flags.LegacyFlags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -79,6 +87,7 @@
/** A class responsible for the life cycle and event handling of the pop-ups over TV view. */
@UiThread
+@AutoFactory
public class TvOverlayManager implements AccessibilityStateChangeListener {
private static final String TAG = "TvOverlayManager";
private static final boolean DEBUG = false;
@@ -216,6 +225,7 @@
private final List<Runnable> mPendingActions = new ArrayList<>();
private final Queue<PendingDialogAction> mPendingDialogActionQueue = new LinkedList<>();
+ private final LegacyFlags mLegacyFlags;
private OnBackStackChangedListener mOnBackStackChangedListener;
@@ -229,12 +239,17 @@
InputBannerView inputBannerView,
SelectInputView selectInputView,
ViewGroup sceneContainer,
- ProgramGuideSearchFragment searchFragment) {
+ ProgramGuideSearchFragment searchFragment,
+ @Provided LegacyFlags legacyFlags,
+ @Provided ChannelDataManager channelDataManager,
+ @Provided TvInputManagerHelper tvInputManager,
+ @Provided ProgramDataManager programDataManager) {
mMainActivity = mainActivity;
mChannelTuner = channelTuner;
TvSingletons singletons = TvSingletons.getSingletons(mainActivity);
- mChannelDataManager = singletons.getChannelDataManager();
- mInputManager = singletons.getTvInputManagerHelper();
+ mLegacyFlags = legacyFlags;
+ mChannelDataManager = channelDataManager;
+ mInputManager = tvInputManager;
mTvView = tvView;
mChannelBannerView = channelBannerView;
mKeypadChannelSwitchView = keypadChannelSwitchView;
@@ -271,7 +286,7 @@
tvView,
optionsManager,
menuView,
- new MenuRowFactory(mainActivity, tvView),
+ new MenuRowFactory(mainActivity, tvView, this.mLegacyFlags),
new Menu.OnMenuVisibilityChangeListener() {
@Override
public void onMenuVisibilityChange(boolean visible) {
@@ -304,9 +319,9 @@
new ProgramGuide(
mainActivity,
channelTuner,
- singletons.getTvInputManagerHelper(),
+ mInputManager,
mChannelDataManager,
- singletons.getProgramDataManager(),
+ programDataManager,
dvrDataManager,
singletons.getDvrScheduleManager(),
singletons.getTracker(),
diff --git a/src/com/android/tv/ui/ViewUtils.java b/src/com/android/tv/ui/ViewUtils.java
index f64a70b..64db7ff 100644
--- a/src/com/android/tv/ui/ViewUtils.java
+++ b/src/com/android/tv/ui/ViewUtils.java
@@ -18,9 +18,11 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
+import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
+
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -33,6 +35,11 @@
}
public static void setTransitionAlpha(View v, float alpha) {
+ /* Begin_AOSP_Before_Q_Comment_Out */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ v.setTransitionAlpha(alpha);
+ }
+ /* End_AOSP_Before_Q_Comment_Out */
Method method;
try {
method = View.class.getDeclaredMethod("setTransitionAlpha", Float.TYPE);
diff --git a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java
index 2726839..de3ae75 100644
--- a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java
+++ b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java
@@ -19,13 +19,14 @@
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
+
import com.android.tv.R;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelDataManager.ChannelListener;
import com.android.tv.data.OnCurrentProgramUpdatedListener;
-import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
public abstract class ChannelCheckItem extends CompoundButtonItem {
private final ChannelDataManager mChannelDataManager;
diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
index 62130b6..b62a57e 100644
--- a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
+++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
@@ -20,7 +20,7 @@
import android.content.SharedPreferences;
import android.media.tv.TvContract.Channels;
import android.os.Bundle;
-import android.support.v17.leanback.widget.VerticalGridView;
+import androidx.leanback.widget.VerticalGridView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
index 36ee5a2..e43568c 100644
--- a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
+++ b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
@@ -16,29 +16,38 @@
package com.android.tv.ui.sidepanel;
-import android.accounts.Account;
import android.app.Activity;
-import android.support.annotation.NonNull;
-import android.util.Log;
-import android.widget.Toast;
+
+import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.TvSingletons;
+import com.android.tv.common.BuildConfig;
import com.android.tv.common.CommonPreferences;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.common.util.CommonUtils;
+import com.android.tv.perf.PerformanceMonitor;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import dagger.android.AndroidInjection;
+import com.android.tv.common.flags.LegacyFlags;
-
-import java.util.ArrayList;
-import java.util.List;
+import javax.inject.Inject;
/** Options for developers only */
public class DeveloperOptionFragment extends SideFragment {
- private static final String TAG = "DeveloperOptionFragment";
private static final String TRACKER_LABEL = "debug options";
+ @Inject Optional<AdditionalDeveloperItemsFactory> mAdditionalDeveloperItemsFactory;
+ @Inject PerformanceMonitor mPerformanceMonitor;
+ @Inject LegacyFlags mLegacyFlags;
+
+ @Override
+ public void onAttach(Activity activity) {
+ AndroidInjection.inject(this);
+ super.onAttach(activity);
+ }
+
@Override
protected String getTitle() {
return getString(R.string.menu_developer_options);
@@ -50,8 +59,15 @@
}
@Override
- protected List<Item> getItemList() {
- List<Item> items = new ArrayList<>();
+ protected ImmutableList<Item> getItemList() {
+ ImmutableList.Builder<Item> items = ImmutableList.builder();
+ if (mAdditionalDeveloperItemsFactory.isPresent()) {
+ items.addAll(
+ mAdditionalDeveloperItemsFactory
+ .get()
+ .getAdditionalDevItems(getMainActivity()));
+ items.add(new DividerItem());
+ }
if (CommonFeatures.DVR.isEnabled(getContext())) {
items.add(
new ActionItem(getString(R.string.dev_item_dvr_history)) {
@@ -61,7 +77,7 @@
}
});
}
- if (CommonUtils.isDeveloper()) {
+ if (BuildConfig.ENG || mLegacyFlags.enableDeveloperFeatures()) {
items.add(
new ActionItem(getString(R.string.dev_item_watch_history)) {
@Override
@@ -87,17 +103,21 @@
CommonPreferences.setStoreTsStream(getContext(), isChecked());
}
});
- if (CommonUtils.isDeveloper()) {
+ if (BuildConfig.ENG || mLegacyFlags.enableDeveloperFeatures()) {
items.add(
new ActionItem(getString(R.string.dev_item_show_performance_monitor_log)) {
@Override
protected void onSelected() {
- TvSingletons.getSingletons(getContext())
- .getPerformanceMonitor()
- .startPerformanceMonitorEventDebugActivity(getContext());
+ mPerformanceMonitor.startPerformanceMonitorEventDebugActivity(
+ getContext());
}
});
}
- return items;
+ return items.build();
+ }
+
+ /** Factory to create additional items. */
+ public interface AdditionalDeveloperItemsFactory {
+ ImmutableList<Item> getAdditionalDevItems(MainActivity mainActivity);
}
}
diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
index aa71fb7..1c03b6a 100644
--- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
@@ -35,7 +35,7 @@
import java.util.ArrayList;
import java.util.List;
-/** Shows Live TV settings. */
+/** Shows TV app settings. */
public class SettingsFragment extends SideFragment {
private static final String TRACKER_LABEL = "settings";
diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java
index 590f130..703b1e4 100644
--- a/src/com/android/tv/ui/sidepanel/SideFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SideFragment.java
@@ -20,24 +20,27 @@
import android.content.Context;
import android.graphics.drawable.RippleDrawable;
import android.os.Bundle;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
+
+import androidx.leanback.widget.VerticalGridView;
+
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.analytics.HasTrackerLabel;
import com.android.tv.analytics.Tracker;
+import com.android.tv.common.dev.DeveloperPreferences;
import com.android.tv.common.util.DurationTimer;
-import com.android.tv.common.util.SystemProperties;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.util.ViewCache;
+
import java.util.List;
public abstract class SideFragment<T extends Item> extends Fragment implements HasTrackerLabel {
@@ -56,7 +59,7 @@
new RecyclerView.RecycledViewPool();
private VerticalGridView mListView;
- private ItemAdapter mAdapter;
+ private ItemAdapter<T> mAdapter;
private SideFragmentListener mListener;
private ChannelDataManager mChannelDataManager;
private ProgramDataManager mProgramDataManager;
@@ -65,6 +68,7 @@
private final int mHideKey;
private final int mDebugHideKey;
+ private Context mContext;
public SideFragment() {
this(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_UNKNOWN);
@@ -73,7 +77,7 @@
/**
* @param hideKey the KeyCode used to hide the fragment
* @param debugHideKey the KeyCode used to hide the fragment if {@link
- * SystemProperties#USE_DEBUG_KEYS}.
+ * DeveloperPreferences#USE_DEBUG_KEYS}.
*/
public SideFragment(int hideKey, int debugHideKey) {
mHideKey = hideKey;
@@ -83,6 +87,7 @@
@Override
public void onAttach(Context context) {
super.onAttach(context);
+ mContext = context;
mChannelDataManager = getMainActivity().getChannelDataManager();
mProgramDataManager = getMainActivity().getProgramDataManager();
mTracker = TvSingletons.getSingletons(context).getTracker();
@@ -129,7 +134,8 @@
}
public final boolean isHideKeyForThisPanel(int keyCode) {
- boolean debugKeysEnabled = SystemProperties.USE_DEBUG_KEYS.getValue();
+ boolean debugKeysEnabled =
+ DeveloperPreferences.USE_DEBUG_KEYS.getDefaultIfContextNull(mContext);
return mHideKey != KeyEvent.KEYCODE_UNKNOWN
&& (mHideKey == keyCode || (debugKeysEnabled && mDebugHideKey == keyCode));
}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java
index b14bf78..620b701 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java
@@ -23,7 +23,7 @@
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
-import android.support.v17.leanback.widget.VerticalGridView;
+import androidx.leanback.widget.VerticalGridView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
index d1ae442..60f8425 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
@@ -16,6 +16,7 @@
package com.android.tv.ui.sidepanel.parentalcontrols;
+import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.media.tv.TvContentRating;
import android.os.Bundle;
@@ -24,9 +25,9 @@
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
+
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.common.experiments.Experiments;
import com.android.tv.dialog.WebDialogFragment;
import com.android.tv.license.LicenseUtils;
import com.android.tv.parental.ContentRatingSystem;
@@ -39,18 +40,28 @@
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.util.TvSettings;
import com.android.tv.util.TvSettings.ContentRatingLevel;
+
import com.google.common.collect.ImmutableList;
+
+import dagger.android.AndroidInjection;
+
+import com.android.tv.common.flags.LegacyFlags;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import javax.inject.Inject;
+
public class RatingsFragment extends SideFragment {
private static final SparseIntArray sLevelResourceIdMap;
private static final SparseIntArray sDescriptionResourceIdMap;
private static final String TRACKER_LABEL = "Ratings";
private int mItemsSize;
+ @Inject LegacyFlags mLegacyFlags;
+
static {
sLevelResourceIdMap = new SparseIntArray(5);
sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_NONE, R.string.option_rating_none);
@@ -101,8 +112,7 @@
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
- if (mBlockUnratedItem != null
- && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) {
+ if (mBlockUnratedItem != null && mLegacyFlags.enableUnratedContentSettings()) {
items.add(mBlockUnratedItem);
items.add(new DividerItem());
}
@@ -158,7 +168,13 @@
super.onCreate(savedInstanceState);
mParentalControlSettings = getMainActivity().getParentalControlSettings();
mParentalControlSettings.loadRatings();
- if (Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) {
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ AndroidInjection.inject(this);
+ super.onAttach(activity);
+ if (mLegacyFlags.enableUnratedContentSettings()) {
mBlockUnratedItem =
new CheckBoxItem(
getResources().getString(R.string.option_block_unrated_programs)) {
@@ -179,6 +195,8 @@
}
}
};
+ } else {
+ mBlockUnratedItem = null;
}
}
@@ -235,8 +253,7 @@
super.onSelected();
mParentalControlSettings.setContentRatingLevel(
getMainActivity().getContentRatingsManager(), mRatingLevel);
- if (mBlockUnratedItem != null
- && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) {
+ if (mBlockUnratedItem != null && mLegacyFlags.enableUnratedContentSettings()) {
// set checked if UNRATED is blocked, and set unchecked otherwise.
mBlockUnratedItem.setChecked(
mParentalControlSettings.isRatingBlocked(
diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java
index b352395..2e9a1ea 100644
--- a/src/com/android/tv/util/AsyncDbTask.java
+++ b/src/com/android/tv/util/AsyncDbTask.java
@@ -28,18 +28,23 @@
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.Range;
+
import com.android.tv.TvSingletons;
import com.android.tv.common.BuildConfig;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.ChannelImpl;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.dvr.data.RecordedProgram;
+
import com.google.common.base.Predicate;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+
import javax.inject.Qualifier;
/**
@@ -123,7 +128,7 @@
return null;
}
if (Utils.isProgramsUri(mUri)
- && TvProviderUtils.checkSeriesIdColumn(context, Programs.CONTENT_URI)) {
+ && TvProviderUtils.checkSeriesIdColumn(context, Programs.CONTENT_URI)) {
mProjection =
TvProviderUtils.addExtraColumnsToProjection(
mProjection, TvProviderUtils.EXTRA_PROGRAM_COLUMN_SERIES_ID);
@@ -323,10 +328,19 @@
}
}
- /** Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}. */
+ /**
+ * Gets an {@link List} of {@link ProgramImpl}s from {@link TvContract.Programs#CONTENT_URI}.
+ */
public abstract static class AsyncProgramQueryTask extends AsyncQueryListTask<Program> {
public AsyncProgramQueryTask(Executor executor, Context context) {
- super(executor, context, Programs.CONTENT_URI, Program.PROJECTION, null, null, null);
+ super(
+ executor,
+ context,
+ Programs.CONTENT_URI,
+ ProgramImpl.PROJECTION,
+ null,
+ null,
+ null);
}
public AsyncProgramQueryTask(
@@ -341,7 +355,7 @@
executor,
context,
uri,
- Program.PROJECTION,
+ ProgramImpl.PROJECTION,
selection,
selectionArgs,
sortOrder,
@@ -350,7 +364,7 @@
@Override
protected final Program fromCursor(Cursor c) {
- return Program.fromCursor(c);
+ return ProgramImpl.fromCursor(c);
}
}
@@ -376,7 +390,7 @@
}
/**
- * Gets an {@link List} of {@link Program}s for a given channel and period {@link
+ * Gets an {@link List} of {@link ProgramImpl}s for a given channel and period {@link
* TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is {@code
* null}, then all the programs is queried.
*/
@@ -410,7 +424,7 @@
}
}
- /** Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}. */
+ /** Gets a single {@link ProgramImpl} from {@link TvContract.Programs#CONTENT_URI}. */
public static class AsyncQueryProgramTask extends AsyncQueryItemTask<Program> {
public AsyncQueryProgramTask(Executor executor, Context context, long programId) {
@@ -418,7 +432,7 @@
executor,
context,
TvContract.buildProgramUri(programId),
- Program.PROJECTION,
+ ProgramImpl.PROJECTION,
null,
null,
null);
@@ -426,7 +440,7 @@
@Override
protected Program fromCursor(Cursor c) {
- return Program.fromCursor(c);
+ return ProgramImpl.fromCursor(c);
}
}
diff --git a/src/com/android/tv/util/OnboardingUtils.java b/src/com/android/tv/util/OnboardingUtils.java
index 3b72e09..4fae2f0 100644
--- a/src/com/android/tv/util/OnboardingUtils.java
+++ b/src/com/android/tv/util/OnboardingUtils.java
@@ -27,7 +27,9 @@
private static final String PREF_KEY_ONBOARDING_VERSION_CODE = "pref_onbaording_versionCode";
private static final int ONBOARDING_VERSION = 1;
- private static final String MERCHANT_COLLECTION_URL_STRING = getMerchantCollectionUrl();
+ // Replace as needed
+ private static final String MERCHANT_COLLECTION_URL_STRING =
+ "https://play.google.com/store/apps/collection/promotion_3001bf9_ATV_livechannels";
/** Intent to show merchant collection in online store. */
public static final Intent ONLINE_STORE_INTENT =
@@ -69,10 +71,5 @@
.apply();
}
- /** Returns merchant collection URL. */
- private static String getMerchantCollectionUrl() {
- return "TODO: add a merchant collection url";
- }
-
private OnboardingUtils() {}
}
diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java
index a9b67fa..52b3e3e 100644
--- a/src/com/android/tv/util/SetupUtils.java
+++ b/src/com/android/tv/util/SetupUtils.java
@@ -307,8 +307,7 @@
}
/**
- * Called when Live channels app is launched. Once it is called, {@link #isFirstTune} will
- * return false.
+ * Called when TV app is launched. Once it is called, {@link #isFirstTune} will return false.
*/
public void onTuned() {
if (!mIsFirstTune) {
diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java
index cb7d985..23c9b49 100644
--- a/src/com/android/tv/util/TvInputManagerHelper.java
+++ b/src/com/android/tv/util/TvInputManagerHelper.java
@@ -46,6 +46,8 @@
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.util.images.ImageCache;
import com.android.tv.util.images.ImageLoader;
+import com.google.common.collect.Ordering;
+import com.android.tv.common.flags.LegacyFlags;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -127,6 +129,7 @@
private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
"com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
private static final String[] mPhysicalTunerBlackList = {
+ "com.google.android.videos", // Play Movies
};
private static final String META_LABEL_SORT_KEY = "input_sort_key";
@@ -158,6 +161,10 @@
}
private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {
+ /* Begin_AOSP_Comment_Out
+ // Disabled partner's tuner input prefix list.
+ "com.mediatek.tvinput/.dtv"
+ End_AOSP_Comment_Out */
};
private static final String[] TESTABLE_INPUTS = {
@@ -292,8 +299,8 @@
private boolean mAllow3rdPartyInputs;
@Inject
- public TvInputManagerHelper(@ApplicationContext Context context) {
- this(context, createTvInputManagerWrapper(context));
+ public TvInputManagerHelper(@ApplicationContext Context context, LegacyFlags legacyFlags) {
+ this(context, createTvInputManagerWrapper(context), legacyFlags);
}
@Nullable
@@ -305,12 +312,14 @@
@VisibleForTesting
protected TvInputManagerHelper(
- Context context, @Nullable TvInputManagerInterface tvInputManager) {
+ Context context,
+ @Nullable TvInputManagerInterface tvInputManager,
+ LegacyFlags legacyFlags) {
mContext = context.getApplicationContext();
mPackageManager = context.getPackageManager();
mTvInputManager = tvInputManager;
mContentRatingsManager = new ContentRatingsManager(context, tvInputManager);
- mParentalControlSettings = new ParentalControlSettings(context);
+ mParentalControlSettings = new ParentalControlSettings(context, legacyFlags);
mTvInputInfoComparator = new InputComparatorInternal(this);
mContentObserver =
new ContentObserver(mHandler) {
@@ -348,7 +357,6 @@
updateAllow3rdPartyInputs();
mTvInputManager.registerCallback(mInternalCallback, mHandler);
initInputMaps();
- mContentRatingsManager.update();
}
public void stop() {
@@ -446,10 +454,12 @@
}
/** Loads label of {@code info}. */
+ @Nullable
public String loadLabel(TvInputInfo info) {
String label = mTvInputLabels.get(info.getId());
if (label == null) {
- label = info.loadLabel(mContext).toString();
+ CharSequence labelSequence = info.loadLabel(mContext);
+ label = labelSequence == null ? null : labelSequence.toString();
mTvInputLabels.put(info.getId(), label);
}
return label;
@@ -703,6 +713,8 @@
@VisibleForTesting
static class InputComparatorInternal implements Comparator<TvInputInfo> {
private final TvInputManagerHelper mInputManager;
+ private static final Ordering<Comparable> NULL_FIRST_STRING_ORDERING =
+ Ordering.natural().nullsFirst();
public InputComparatorInternal(TvInputManagerHelper inputManager) {
mInputManager = inputManager;
@@ -713,7 +725,8 @@
if (mInputManager.isPartnerInput(lhs) != mInputManager.isPartnerInput(rhs)) {
return mInputManager.isPartnerInput(lhs) ? -1 : 1;
}
- return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs));
+ return NULL_FIRST_STRING_ORDERING.compare(
+ mInputManager.loadLabel(lhs), mInputManager.loadLabel(rhs));
}
}
@@ -795,7 +808,7 @@
if (TextUtils.isEmpty(label)) {
label = mTvInputManagerHelper.loadLabel(input);
}
- return label;
+ return label == null ? "" : label;
}
private int getPriority(TvInputInfo info) {
diff --git a/src/com/android/tv/util/TvProviderUtils.java b/src/com/android/tv/util/TvProviderUtils.java
index 6b5aaec..d931dcd 100644
--- a/src/com/android/tv/util/TvProviderUtils.java
+++ b/src/com/android/tv/util/TvProviderUtils.java
@@ -27,7 +27,7 @@
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.util.Log;
-import com.android.tv.data.BaseProgram;
+import com.android.tv.data.api.BaseProgram;
import com.android.tv.features.PartnerFeatures;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -67,6 +67,9 @@
@WorkerThread
public static synchronized boolean checkSeriesIdColumn(Context context, Uri uri) {
boolean canCreateColumn = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
+ canCreateColumn =
+ (canCreateColumn
+ || PartnerFeatures.TVPROVIDER_ALLOWS_COLUMN_CREATION.isEnabled(context));
if (!canCreateColumn) {
return false;
}
@@ -112,6 +115,9 @@
@WorkerThread
public static synchronized boolean checkStateColumn(Context context, Uri uri) {
boolean canCreateColumn = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
+ canCreateColumn =
+ (canCreateColumn
+ || PartnerFeatures.TVPROVIDER_ALLOWS_COLUMN_CREATION.isEnabled(context));
if (!canCreateColumn) {
return false;
}
@@ -144,8 +150,8 @@
return TRUE.equals(sRecordedProgramHasStateColumn);
}
- public static String[] addExtraColumnsToProjection(String[] projection,
- @TvProviderExtraColumn String column) {
+ public static String[] addExtraColumnsToProjection(
+ String[] projection, @TvProviderExtraColumn String column) {
List<String> projectionList = new ArrayList<>(Arrays.asList(projection));
if (!projectionList.contains(column)) {
projectionList.add(column);
diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java
index 5117373..c1f9e93 100644
--- a/src/com/android/tv/util/Utils.java
+++ b/src/com/android/tv/util/Utils.java
@@ -39,17 +39,19 @@
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
+
import com.android.tv.R;
import com.android.tv.TvSingletons;
import com.android.tv.common.BaseSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.util.Clock;
import com.android.tv.data.GenreItems;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.StreamInfo;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
@@ -324,7 +326,7 @@
TvContract.buildChannelUri(channelId), timeMs, timeMs);
ContentResolver resolver = context.getContentResolver();
- String[] projection = Program.PROJECTION;
+ String[] projection = ProgramImpl.PROJECTION;
if (TvProviderUtils.checkSeriesIdColumn(context, TvContract.Programs.CONTENT_URI)) {
if (Utils.isProgramsUri(uri)) {
projection =
@@ -334,7 +336,7 @@
}
try (Cursor cursor = resolver.query(uri, projection, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
- return Program.fromCursor(cursor);
+ return ProgramImpl.fromCursor(cursor);
}
}
return null;
@@ -603,6 +605,7 @@
}
/** Returns the label for a given input. Returns the custom label, if any. */
+ @Nullable
public static String loadLabel(Context context, TvInputInfo input) {
if (input == null) {
return null;
@@ -612,7 +615,7 @@
CharSequence customLabel = inputManager.loadCustomLabel(input);
String label = (customLabel == null) ? null : customLabel.toString();
if (TextUtils.isEmpty(label)) {
- label = inputManager.loadLabel(input).toString();
+ label = inputManager.loadLabel(input);
}
return label;
}
@@ -714,33 +717,6 @@
return context.createConfigurationContext(config).getText(resourceId);
}
- /** Checks where there is any internal TV input. */
- public static boolean hasInternalTvInputs(Context context, boolean tunerInputOnly) {
- for (TvInputInfo input :
- TvSingletons.getSingletons(context)
- .getTvInputManagerHelper()
- .getTvInputInfos(true, tunerInputOnly)) {
- if (isInternalTvInput(context, input.getId())) {
- return true;
- }
- }
- return false;
- }
-
- /** Returns the internal TV inputs. */
- public static List<TvInputInfo> getInternalTvInputs(Context context, boolean tunerInputOnly) {
- List<TvInputInfo> inputs = new ArrayList<>();
- for (TvInputInfo input :
- TvSingletons.getSingletons(context)
- .getTvInputManagerHelper()
- .getTvInputInfos(true, tunerInputOnly)) {
- if (isInternalTvInput(context, input.getId())) {
- inputs.add(input);
- }
- }
- return inputs;
- }
-
/** Checks whether the input is internal or not. */
public static boolean isInternalTvInput(Context context, String inputId) {
ComponentName unflattenInputId = ComponentName.unflattenFromString(inputId);
diff --git a/src/com/android/tv/util/account/AccountHelper.java b/src/com/android/tv/util/account/AccountHelper.java
index e98b42e..d54c2f5 100644
--- a/src/com/android/tv/util/account/AccountHelper.java
+++ b/src/com/android/tv/util/account/AccountHelper.java
@@ -35,4 +35,11 @@
/** Returns all eligible accounts . */
@Nullable
Account getFirstEligibleAccount();
+
+ /**
+ * Initialize the account helper.
+ *
+ * <p>This method is a no op if called more than once.
+ */
+ void init();
}
diff --git a/src/com/android/tv/util/account/AccountHelperImpl.java b/src/com/android/tv/util/account/AccountHelperImpl.java
index 58fbd27..e97cc3f 100644
--- a/src/com/android/tv/util/account/AccountHelperImpl.java
+++ b/src/com/android/tv/util/account/AccountHelperImpl.java
@@ -21,8 +21,12 @@
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
+import javax.inject.Inject;
+import javax.inject.Singleton;
/** Helper methods for getting and selecting a user account. */
+@Singleton
public class AccountHelperImpl implements com.android.tv.util.account.AccountHelper {
private static final String SELECTED_ACCOUNT = "android.tv.livechannels.selected_account";
@@ -31,7 +35,8 @@
@Nullable private Account mSelectedAccount;
- public AccountHelperImpl(Context context) {
+ @Inject
+ public AccountHelperImpl(@ApplicationContext Context context) {
mContext = context.getApplicationContext();
mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
}
@@ -98,4 +103,9 @@
PreferenceManager.getDefaultSharedPreferences(mContext);
defaultPreferences.edit().putString(SELECTED_ACCOUNT, account.name).commit();
}
+
+ @Override
+ public void init() {
+ // do nothing.
+ }
}
diff --git a/src/com/android/tv/util/images/ImageLoader.java b/src/com/android/tv/util/images/ImageLoader.java
index d2ad0eb..e026f26 100644
--- a/src/com/android/tv/util/images/ImageLoader.java
+++ b/src/com/android/tv/util/images/ImageLoader.java
@@ -29,9 +29,13 @@
import android.support.annotation.WorkerThread;
import android.util.ArraySet;
import android.util.Log;
+
+import androidx.tvprovider.media.tv.TvContractCompat.PreviewPrograms;
+
import com.android.tv.R;
import com.android.tv.common.concurrent.NamedThreadFactory;
import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo;
+
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
@@ -164,8 +168,8 @@
* @return {@code true} if the load is complete and the callback is executed.
*/
@UiThread
- public static boolean loadBitmap(
- Context context, String uriString, ImageLoaderCallback callback) {
+ public static <T> boolean loadBitmap(
+ Context context, String uriString, ImageLoaderCallback<T> callback) {
return loadBitmap(context, uriString, Integer.MAX_VALUE, Integer.MAX_VALUE, callback);
}
@@ -178,12 +182,12 @@
* @return {@code true} if the load is complete and the callback is executed.
*/
@UiThread
- public static boolean loadBitmap(
+ public static <T> boolean loadBitmap(
Context context,
String uriString,
int maxWidth,
int maxHeight,
- ImageLoaderCallback callback) {
+ ImageLoaderCallback<T> callback) {
if (DEBUG) {
Log.d(TAG, "loadBitmap() " + uriString);
}
@@ -191,12 +195,12 @@
context, uriString, maxWidth, maxHeight, callback, IMAGE_THREAD_POOL_EXECUTOR);
}
- private static boolean doLoadBitmap(
+ private static <T> boolean doLoadBitmap(
Context context,
String uriString,
int maxWidth,
int maxHeight,
- ImageLoaderCallback callback,
+ ImageLoaderCallback<T> callback,
Executor executor) {
// Check the cache before creating a Task. The cache will be checked again in doLoadBitmap
// but checking a cache is much cheaper than creating an new task.
@@ -222,7 +226,8 @@
* @return {@code true} if the load is complete and the callback is executed.
*/
@UiThread
- public static boolean loadBitmap(ImageLoaderCallback callback, LoadBitmapTask loadBitmapTask) {
+ public static <T> boolean loadBitmap(
+ ImageLoaderCallback<T> callback, LoadBitmapTask<T> loadBitmapTask) {
if (DEBUG) {
Log.d(TAG, "loadBitmap() " + loadBitmapTask);
}
@@ -231,8 +236,8 @@
/** @return {@code true} if the load is complete and the callback is executed. */
@UiThread
- private static boolean doLoadBitmap(
- ImageLoaderCallback callback, Executor executor, LoadBitmapTask loadBitmapTask) {
+ private static <T> boolean doLoadBitmap(
+ ImageLoaderCallback<T> callback, Executor executor, LoadBitmapTask<T> loadBitmapTask) {
ScaledBitmapInfo bitmapInfo = loadBitmapTask.getFromCache();
boolean needToReload = loadBitmapTask.isReloadNeeded();
if (bitmapInfo != null && !needToReload) {
@@ -267,11 +272,11 @@
*
* <p>Implement {@link #doGetBitmapInBackground} to do the actual loading.
*/
- public abstract static class LoadBitmapTask extends AsyncTask<Void, Void, ScaledBitmapInfo> {
+ public abstract static class LoadBitmapTask<T> extends AsyncTask<Void, Void, ScaledBitmapInfo> {
protected final Context mAppContext;
protected final int mMaxWidth;
protected final int mMaxHeight;
- private final Set<ImageLoaderCallback> mCallbacks = new ArraySet<>();
+ private final Set<ImageLoaderCallback<T>> mCallbacks = new ArraySet<>();
private final ImageCache mImageCache;
private final String mKey;
@@ -353,7 +358,7 @@
public final void onPostExecute(ScaledBitmapInfo scaledBitmapInfo) {
if (DEBUG) Log.d(ImageLoader.TAG, "Bitmap is loaded " + mKey);
- for (ImageLoader.ImageLoaderCallback callback : mCallbacks) {
+ for (ImageLoader.ImageLoaderCallback<T> callback : mCallbacks) {
callback.onBitmapLoaded(scaledBitmapInfo == null ? null : scaledBitmapInfo.bitmap);
}
ImageLoader.sPendingListMap.remove(mKey);
@@ -376,7 +381,7 @@
}
}
- private static final class LoadBitmapFromUriTask extends LoadBitmapTask {
+ private static final class LoadBitmapFromUriTask<T> extends LoadBitmapTask<T> {
private LoadBitmapFromUriTask(
Context context,
ImageCache imageCache,
@@ -395,7 +400,7 @@
}
/** Loads and caches the logo for a given {@link TvInputInfo} */
- public static final class LoadTvInputLogoTask extends LoadBitmapTask {
+ public static final class LoadTvInputLogoTask<T> extends LoadBitmapTask<T> {
private final TvInputInfo mInfo;
public LoadTvInputLogoTask(Context context, ImageCache cache, TvInputInfo info) {
@@ -414,9 +419,10 @@
@Override
public ScaledBitmapInfo doGetBitmapInBackground() {
Drawable drawable = mInfo.loadIcon(mAppContext);
- Bitmap bm = drawable instanceof BitmapDrawable
- ? ((BitmapDrawable) drawable).getBitmap()
- : BitmapUtils.drawableToBitmap(drawable);
+ Bitmap bm =
+ drawable instanceof BitmapDrawable
+ ? ((BitmapDrawable) drawable).getBitmap()
+ : BitmapUtils.drawableToBitmap(drawable);
return bm == null
? null
: BitmapUtils.createScaledBitmapInfo(getKey(), bm, mMaxWidth, mMaxHeight);
@@ -428,6 +434,46 @@
}
}
+ /**
+ * Calculates Aspect Ratio of Poster Art from Uri.
+ *
+ * <p><b>Note</b> the function will check the cache before loading the bitmap
+ *
+ * @return the Aspect Ratio of the Poster Art.
+ */
+ public static int getAspectRatioFromPosterArtUri(Context context, String uriString) {
+ // Check the cache before loading the bitmap.
+ ImageCache imageCache = ImageCache.getInstance();
+ ScaledBitmapInfo bitmapInfo = imageCache.get(uriString);
+ int bitmapWidth;
+ int bitmapHeight;
+ float bitmapAspectRatio;
+ int aspectRatio;
+ if (bitmapInfo == null) {
+ bitmapInfo =
+ BitmapUtils.decodeSampledBitmapFromUriString(
+ context, uriString, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ }
+ bitmapWidth = bitmapInfo.bitmap.getWidth();
+ bitmapHeight = bitmapInfo.bitmap.getHeight();
+ bitmapAspectRatio = (float) bitmapWidth / bitmapHeight;
+ /* Assign nearest aspect ratio from the defined values in Preview Programs */
+ if (bitmapAspectRatio > 0 && bitmapAspectRatio <= 0.6803) {
+ aspectRatio = PreviewPrograms.ASPECT_RATIO_2_3;
+ } else if (bitmapAspectRatio > 0.6803 && bitmapAspectRatio <= 0.8469) {
+ aspectRatio = PreviewPrograms.ASPECT_RATIO_MOVIE_POSTER;
+ } else if (bitmapAspectRatio > 0.8469 && bitmapAspectRatio <= 1.1667) {
+ aspectRatio = PreviewPrograms.ASPECT_RATIO_1_1;
+ } else if (bitmapAspectRatio > 1.1667 && bitmapAspectRatio <= 1.4167) {
+ aspectRatio = PreviewPrograms.ASPECT_RATIO_4_3;
+ } else if (bitmapAspectRatio > 1.4167 && bitmapAspectRatio <= 1.6389) {
+ aspectRatio = PreviewPrograms.ASPECT_RATIO_3_2;
+ } else {
+ aspectRatio = PreviewPrograms.ASPECT_RATIO_16_9;
+ }
+ return aspectRatio;
+ }
+
private static synchronized Handler getMainHandler() {
if (sMainHandler == null) {
sMainHandler = new Handler(Looper.getMainLooper());
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
new file mode 100644
index 0000000..a3b6bb2
--- /dev/null
+++ b/tests/common/Android.bp
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_library {
+ name: "tv-test-common",
+
+ // Include all test java files.
+ srcs: [
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ],
+
+ static_libs: [
+ "android-support-annotations",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "tv-guava-android-jar",
+ "mockito-target",
+ "tv-lib-truth",
+ "ub-uiautomator",
+ "Robolectric_all-target",
+ ],
+
+ // Link tv-common as shared library to avoid the problem of initialization of the constants
+ libs: [
+ "tv-common",
+ "LiveTv",
+ ],
+
+ sdk_version: "system_current",
+
+ resource_dirs: ["res"],
+ aidl: {
+ local_include_dirs: ["src"],
+ },
+
+}
diff --git a/tests/common/Android.mk b/tests/common/Android.mk
deleted file mode 100644
index 7a111d0..0000000
--- a/tests/common/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include all test java files.
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-Iaidl-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-annotations \
- androidx.test.runner \
- androidx.test.rules \
- tv-guava-android-jar \
- mockito-target \
- tv-lib-truth \
- ub-uiautomator \
-
-# Link tv-common as shared library to avoid the problem of initialization of the constants
-LOCAL_JAVA_LIBRARIES := tv-common
-
-LOCAL_INSTRUMENTATION_FOR := LiveTv
-LOCAL_MODULE := tv-test-common
-LOCAL_MODULE_TAGS := optional
-LOCAL_SDK_VERSION := system_current
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/src
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/common/AndroidManifest.xml b/tests/common/AndroidManifest.xml
index 3a769a8..ec9614d 100644
--- a/tests/common/AndroidManifest.xml
+++ b/tests/common/AndroidManifest.xml
@@ -18,6 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.testing"
android:versionCode="1">
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application />
</manifest>
diff --git a/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java b/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java
index ba4662e..1592897 100644
--- a/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java
+++ b/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java
@@ -16,6 +16,8 @@
package com.android.tv.testing;
+import static com.google.common.truth.Fact.simpleFact;
+
import android.support.annotation.Nullable;
import com.android.tv.data.ChannelNumber;
import com.google.common.truth.ComparableSubject;
@@ -24,8 +26,7 @@
import com.google.common.truth.Truth;
/** Propositions for {@link ChannelNumber} subjects. */
-public final class ChannelNumberSubject
- extends ComparableSubject<ChannelNumberSubject, ChannelNumber> {
+public final class ChannelNumberSubject extends ComparableSubject {
private static final Subject.Factory<ChannelNumberSubject, ChannelNumber> FACTORY =
ChannelNumberSubject::new;
@@ -37,30 +38,30 @@
return Truth.assertAbout(channelNumbers()).that(actual);
}
- public ChannelNumberSubject(FailureMetadata failureMetadata, @Nullable ChannelNumber subject) {
- super(failureMetadata, subject);
+ private final ChannelNumber actual;
+
+ public ChannelNumberSubject(FailureMetadata failureMetadata, @Nullable ChannelNumber subject) {
+ super(failureMetadata, subject);
+ this.actual = subject;
}
public void displaysAs(int major) {
- if (!getSubject().majorNumber.equals(Integer.toString(major))
- || getSubject().hasDelimiter) {
- fail("displaysAs", major);
+ if (!actual.majorNumber.equals(Integer.toString(major)) || actual.hasDelimiter) {
+ failWithActual("expected to display as", major);
}
}
public void displaysAs(int major, int minor) {
- if (!getSubject().majorNumber.equals(Integer.toString(major))
- || !getSubject().minorNumber.equals(Integer.toString(minor))
- || !getSubject().hasDelimiter) {
- fail("displaysAs", major + "-" + minor);
+ if (!actual.majorNumber.equals(Integer.toString(major))
+ || !actual.minorNumber.equals(Integer.toString(minor))
+ || !actual.hasDelimiter) {
+ failWithActual("expected to display as", major + "-" + minor);
}
}
public void isEmpty() {
- if (!getSubject().majorNumber.isEmpty()
- || !getSubject().minorNumber.isEmpty()
- || getSubject().hasDelimiter) {
- fail("isEmpty");
+ if (!actual.majorNumber.isEmpty() || !actual.minorNumber.isEmpty() || actual.hasDelimiter) {
+ failWithActual(simpleFact("expected to be empty"));
}
}
}
diff --git a/tests/common/src/com/android/tv/testing/EpgTestData.java b/tests/common/src/com/android/tv/testing/EpgTestData.java
index 362f336..d22bd28 100644
--- a/tests/common/src/com/android/tv/testing/EpgTestData.java
+++ b/tests/common/src/com/android/tv/testing/EpgTestData.java
@@ -18,13 +18,17 @@
import com.android.tv.data.ChannelImpl;
import com.android.tv.data.Lineup;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+import com.android.tv.testing.fakes.FakeClock;
+
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
+
import java.util.concurrent.TimeUnit;
/** EPG data for use in tests. */
@@ -75,14 +79,14 @@
// Start and end time may be negative meaning they happen before "now".
public static final Program PROGRAM_1 =
- new Program.Builder()
+ new ProgramImpl.Builder()
.setTitle("Program 1")
.setStartTimeUtcMillis(0)
.setEndTimeUtcMillis(TimeUnit.MINUTES.toMillis(30))
.build();
public static final Program PROGRAM_2 =
- new Program.Builder()
+ new ProgramImpl.Builder()
.setTitle("Program 2")
.setStartTimeUtcMillis(TimeUnit.MINUTES.toMillis(30))
.setEndTimeUtcMillis(TimeUnit.MINUTES.toMillis(60))
@@ -191,7 +195,7 @@
new Function<Program, Program>() {
@Override
public Program apply(Program p) {
- return new Program.Builder(p)
+ return new ProgramImpl.Builder(p)
.setStartTimeUtcMillis(p.getStartTimeUtcMillis() + time)
.setEndTimeUtcMillis(p.getEndTimeUtcMillis() + time)
.build();
diff --git a/tests/common/src/com/android/tv/testing/FakeEpgReader.java b/tests/common/src/com/android/tv/testing/FakeEpgReader.java
index fb35c65..24afe8e 100644
--- a/tests/common/src/com/android/tv/testing/FakeEpgReader.java
+++ b/tests/common/src/com/android/tv/testing/FakeEpgReader.java
@@ -19,13 +19,17 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Range;
+
import com.android.tv.data.ChannelImpl;
import com.android.tv.data.ChannelNumber;
import com.android.tv.data.Lineup;
-import com.android.tv.data.Program;
+import com.android.tv.data.ProgramImpl;
import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
import com.android.tv.data.epg.EpgReader;
import com.android.tv.dvr.data.SeriesInfo;
+import com.android.tv.testing.fakes.FakeClock;
+
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
@@ -33,6 +37,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
+
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -144,7 +149,7 @@
@Nullable
@Override
public Program apply(@Nullable Program program) {
- return new Program.Builder(program)
+ return new ProgramImpl.Builder(program)
.setChannelId(channel.getChannel().getId())
.setPackageName(channel.getChannel().getPackageName())
.build();
diff --git a/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java b/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java
index 85bdcf0..be08385 100644
--- a/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java
+++ b/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java
@@ -17,13 +17,14 @@
package com.android.tv.testing;
import android.content.Context;
+import com.android.tv.common.flags.impl.DefaultLegacyFlags;
import com.android.tv.util.TvInputManagerHelper;
/** Fake TvInputManagerHelper. */
public class FakeTvInputManagerHelper extends TvInputManagerHelper {
public FakeTvInputManagerHelper(Context context) {
- super(context, new FakeTvInputManager());
+ super(context, new FakeTvInputManager(), DefaultLegacyFlags.DEFAULT);
}
public FakeTvInputManager getFakeTvInputManager() {
diff --git a/tests/common/src/com/android/tv/testing/TestSingletonApp.java b/tests/common/src/com/android/tv/testing/TestSingletonApp.java
index f1a98ff..e233d95 100644
--- a/tests/common/src/com/android/tv/testing/TestSingletonApp.java
+++ b/tests/common/src/com/android/tv/testing/TestSingletonApp.java
@@ -19,25 +19,23 @@
import android.app.Application;
import android.media.tv.TvInputManager;
import android.os.AsyncTask;
+
import com.android.tv.InputSessionManager;
import com.android.tv.MainActivityWrapper;
import com.android.tv.TvSingletons;
import com.android.tv.analytics.Analytics;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.BaseApplication;
-import com.android.tv.common.experiments.ExperimentLoader;
import com.android.tv.common.flags.impl.DefaultBackendKnobsFlags;
import com.android.tv.common.flags.impl.DefaultCloudEpgFlags;
-import com.android.tv.common.flags.impl.DefaultConcurrentDvrPlaybackFlags;
-import com.android.tv.common.flags.impl.DefaultTunerFlags;
import com.android.tv.common.flags.impl.DefaultUiFlags;
+import com.android.tv.common.flags.impl.SettableFlagsModule;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.singletons.HasSingletons;
import com.android.tv.common.util.Clock;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.ProgramDataManager;
-import com.android.tv.data.epg.EpgFetcher;
import com.android.tv.data.epg.EpgReader;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
@@ -47,47 +45,38 @@
import com.android.tv.perf.PerformanceMonitor;
import com.android.tv.perf.stub.StubPerformanceMonitor;
import com.android.tv.testing.dvr.DvrDataManagerInMemoryImpl;
+import com.android.tv.testing.fakes.FakeClock;
import com.android.tv.testing.testdata.TestData;
import com.android.tv.tuner.singletons.TunerSingletons;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.tuner.source.TunerTsStreamerManager;
-import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
-import com.android.tv.tuner.tvinput.factory.TunerSessionFactoryImpl;
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.android.tv.util.account.AccountHelper;
-import com.google.common.base.Optional;
-import java.util.concurrent.Executor;
-import javax.inject.Provider;
-/** Test application for Live TV. */
+import com.google.common.base.Optional;
+
+import dagger.Lazy;
+
+import java.util.concurrent.Executor;
+
+/** Test application for TV app. */
public class TestSingletonApp extends Application
implements TvSingletons, TunerSingletons, HasSingletons<TvSingletons> {
public final FakeClock fakeClock = FakeClock.createWithCurrentTime();
public final FakeEpgReader epgReader = new FakeEpgReader(fakeClock);
public final FakeEpgFetcher epgFetcher = new FakeEpgFetcher();
+ public final SettableFlagsModule flagsModule = new SettableFlagsModule();
public FakeTvInputManagerHelper tvInputManagerHelper;
public SetupUtils setupUtils;
public DvrManager dvrManager;
public DvrDataManager mDvrDataManager;
+ @DbExecutor public Executor dbExecutor = AsyncTask.SERIAL_EXECUTOR;
- private final Provider<EpgReader> mEpgReaderProvider = SingletonProvider.create(epgReader);
+ private final Lazy<EpgReader> mEpgReaderProvider = () -> epgReader;
private final Optional<BuiltInTunerManager> mBuiltInTunerManagerOptional = Optional.absent();
- private final DefaultBackendKnobsFlags mBackendKnobs = new DefaultBackendKnobsFlags();
- private final DefaultCloudEpgFlags mCloudEpgFlags = new DefaultCloudEpgFlags();
- private final DefaultUiFlags mUiFlags = new DefaultUiFlags();
- private final DefaultConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags =
- new DefaultConcurrentDvrPlaybackFlags();
- private final TsDataSourceManager.Factory mTsDataSourceManagerFactory =
- new TsDataSourceManager.Factory(() -> new TunerTsStreamerManager(null));
- private final TunerSessionFactoryImpl mTunerSessionFactory =
- new TunerSessionFactoryImpl(
- new DefaultTunerFlags(),
- mConcurrentDvrPlaybackFlags,
- mTsDataSourceManagerFactory);
- private PerformanceMonitor mPerformanceMonitor;
+
+ private final PerformanceMonitor mPerformanceMonitor = new StubPerformanceMonitor();
private ChannelDataManager mChannelDataManager;
@Override
@@ -96,7 +85,9 @@
tvInputManagerHelper = new FakeTvInputManagerHelper(this);
setupUtils = new SetupUtils(this, mBuiltInTunerManagerOptional);
tvInputManagerHelper.start();
- mChannelDataManager = new ChannelDataManager(this, tvInputManagerHelper);
+ mChannelDataManager =
+ new ChannelDataManager(
+ this, tvInputManagerHelper, dbExecutor, getContentResolver());
mChannelDataManager.start();
mDvrDataManager = new DvrDataManagerInMemoryImpl(this, fakeClock);
// HACK reset the singleton for tests
@@ -124,21 +115,11 @@
}
@Override
- public boolean isChannelDataManagerLoadFinished() {
- return false;
- }
-
- @Override
public ProgramDataManager getProgramDataManager() {
return null;
}
@Override
- public boolean isProgramDataManagerCurrentProgramsLoadFinished() {
- return false;
- }
-
- @Override
public PreviewDataManager getPreviewDataManager() {
return null;
}
@@ -184,16 +165,11 @@
}
@Override
- public Provider<EpgReader> providesEpgReader() {
+ public Lazy<EpgReader> providesEpgReader() {
return mEpgReaderProvider;
}
@Override
- public EpgFetcher getEpgFetcher() {
- return epgFetcher;
- }
-
- @Override
public SetupUtils getSetupUtils() {
return setupUtils;
}
@@ -204,21 +180,11 @@
}
@Override
- public ExperimentLoader getExperimentLoader() {
- return new ExperimentLoader();
- }
-
- @Override
public MainActivityWrapper getMainActivityWrapper() {
return null;
}
@Override
- public AccountHelper getAccountHelper() {
- return null;
- }
-
- @Override
public Clock getClock() {
return fakeClock;
}
@@ -235,9 +201,6 @@
@Override
public PerformanceMonitor getPerformanceMonitor() {
- if (mPerformanceMonitor == null) {
- mPerformanceMonitor = new StubPerformanceMonitor();
- }
return mPerformanceMonitor;
}
@@ -248,22 +211,22 @@
@Override
public Executor getDbExecutor() {
- return AsyncTask.SERIAL_EXECUTOR;
+ return dbExecutor;
}
@Override
public DefaultBackendKnobsFlags getBackendKnobs() {
- return mBackendKnobs;
+ return flagsModule.backendKnobsFlags;
}
@Override
public DefaultCloudEpgFlags getCloudEpgFlags() {
- return mCloudEpgFlags;
+ return flagsModule.cloudEpgFlags;
}
@Override
public DefaultUiFlags getUiFlags() {
- return mUiFlags;
+ return flagsModule.uiFlags;
}
@Override
@@ -272,15 +235,6 @@
}
@Override
- public DefaultConcurrentDvrPlaybackFlags getConcurrentDvrPlaybackFlags() {
- return mConcurrentDvrPlaybackFlags;
- }
-
- public TunerSessionFactory getTunerSessionFactory() {
- return mTunerSessionFactory;
- }
-
- @Override
public TvSingletons singletons() {
return this;
}
diff --git a/tests/common/src/com/android/tv/testing/FakeClock.java b/tests/common/src/com/android/tv/testing/fakes/FakeClock.java
similarity index 98%
rename from tests/common/src/com/android/tv/testing/FakeClock.java
rename to tests/common/src/com/android/tv/testing/fakes/FakeClock.java
index f594193..adef3cd 100644
--- a/tests/common/src/com/android/tv/testing/FakeClock.java
+++ b/tests/common/src/com/android/tv/testing/fakes/FakeClock.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.testing;
+package com.android.tv.testing.fakes;
import com.android.tv.common.util.Clock;
import java.util.concurrent.TimeUnit;
diff --git a/tests/common/src/com/android/tv/testing/FakeTvProvider.java b/tests/common/src/com/android/tv/testing/fakes/FakeTvProvider.java
similarity index 99%
rename from tests/common/src/com/android/tv/testing/FakeTvProvider.java
rename to tests/common/src/com/android/tv/testing/fakes/FakeTvProvider.java
index 20903c6..36e97bc 100644
--- a/tests/common/src/com/android/tv/testing/FakeTvProvider.java
+++ b/tests/common/src/com/android/tv/testing/fakes/FakeTvProvider.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.testing;
+package com.android.tv.testing.fakes;
import android.annotation.SuppressLint;
import android.content.ContentProvider;
@@ -54,7 +54,7 @@
import androidx.tvprovider.media.tv.TvContractCompat.Programs.Genres;
import androidx.tvprovider.media.tv.TvContractCompat.RecordedPrograms;
import androidx.tvprovider.media.tv.TvContractCompat.WatchNextPrograms;
-import com.android.tv.util.SqlParams;
+import com.android.tv.common.util.sql.SqlParams;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
diff --git a/tests/common/src/com/android/tv/testing/robo/ContentProviders.java b/tests/common/src/com/android/tv/testing/robo/ContentProviders.java
new file mode 100644
index 0000000..aaaa11d
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/robo/ContentProviders.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.testing.robo;
+
+import android.content.ContentProvider;
+import android.content.pm.ProviderInfo;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ContentProviderController;
+import org.robolectric.shadows.ShadowContentResolver;
+
+/** Static utilities for using content providers in tests. */
+public final class ContentProviders {
+
+ /** Builds creates and register a ContentProvider with the given authority. */
+ public static <T extends ContentProvider> T register(Class<T> providerClass, String authority) {
+ ProviderInfo info = new ProviderInfo();
+ info.authority = authority;
+ ContentProviderController<T> contentProviderController =
+ Robolectric.buildContentProvider(providerClass);
+ T provider = contentProviderController.create(info).get();
+ provider.onCreate();
+ ShadowContentResolver.registerProviderInternal(authority, provider);
+ return provider;
+ }
+
+ private ContentProviders() {}
+}
diff --git a/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java b/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java
new file mode 100644
index 0000000..ad91f3d
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 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.testing.robo;
+
+import android.media.tv.TvContract;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.fakes.FakeTvProvider;
+import com.android.tv.testing.testdata.TestData;
+import java.util.concurrent.TimeUnit;
+import org.robolectric.Robolectric;
+
+/** Static utilities for using {@link TestSingletonApp} in roboletric tests. */
+public final class RobotTestAppHelper {
+
+ public static void loadTestData(TestSingletonApp app, TestData testData) {
+ ContentProviders.register(FakeTvProvider.class, TvContract.AUTHORITY);
+ app.loadTestData(testData, TimeUnit.DAYS.toMillis(1));
+ Robolectric.flushBackgroundThreadScheduler();
+ Robolectric.flushForegroundThreadScheduler();
+ }
+
+ private RobotTestAppHelper() {}
+}
diff --git a/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java b/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java
new file mode 100644
index 0000000..5a2c41e
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java
@@ -0,0 +1,89 @@
+/*
+ * 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.testing.shadows;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow {@link MediaSession}. */
+@Implements(MediaSession.class)
+public class ShadowMediaSession {
+
+ public MediaSession.Callback mCallback;
+ public PendingIntent mMediaButtonReceiver;
+ public PendingIntent mSessionActivity;
+ public PlaybackState mPlaybackState;
+ public MediaMetadata mMediaMetadata;
+ public int mFlags;
+ public boolean mActive;
+ public boolean mReleased;
+
+ /** Stand-in for the MediaSession constructor with the same parameters. */
+ public void __constructor__(Context context, String tag, int userID) {
+ // This empty method prevents the real MediaSession constructor from being called.
+ }
+
+ @Implementation
+ public void setCallback(MediaSession.Callback callback) {
+ mCallback = callback;
+ }
+
+ @Implementation
+ public void setMediaButtonReceiver(PendingIntent mbr) {
+ mMediaButtonReceiver = mbr;
+ }
+
+ @Implementation
+ public void setSessionActivity(PendingIntent activity) {
+ mSessionActivity = activity;
+ }
+
+ @Implementation
+ public void setPlaybackState(PlaybackState state) {
+ mPlaybackState = state;
+ }
+
+ @Implementation
+ public void setMetadata(MediaMetadata metadata) {
+ mMediaMetadata = metadata;
+ }
+
+ @Implementation
+ public void setFlags(int flags) {
+ mFlags = flags;
+ }
+
+ @Implementation
+ public boolean isActive() {
+ return mActive;
+ }
+
+ @Implementation
+ public void setActive(boolean active) {
+ mActive = active;
+ }
+
+ @Implementation
+ public void release() {
+ mReleased = true;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
index 4b7c1f8..30fbf37 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
@@ -30,7 +30,7 @@
import com.android.tv.testing.utils.Utils;
import junit.framework.Assert;
-/** Helper for testing the Live TV Application. */
+/** Helper for testing the TV application. */
public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper {
private static final String TAG = "LiveChannelsUiDevice";
private static final int APPLICATION_START_TIMEOUT_MSEC = 5000;
@@ -56,7 +56,7 @@
waitForCondition(mUiDevice, Until.hasObject(Constants.TV_VIEW));
Assert.assertTrue(
- Constants.TV_APP_PACKAGE + " did not start",
+ Constants.TV_APP_PACKAGE + " did not start",
mUiDevice.wait(
Until.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0)),
APPLICATION_START_TIMEOUT_MSEC));
diff --git a/tests/func/AndroidManifest.xml b/tests/func/AndroidManifest.xml
index 3d7d775..e60773f 100644
--- a/tests/func/AndroidManifest.xml
+++ b/tests/func/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.tests.ui" >
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23" />
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23" />
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java
index efc7ecf..e24c72f 100644
--- a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java
+++ b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java
@@ -135,7 +135,7 @@
controller.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
controller.menuHelper.assertWaitForMenu();
assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE);
- // Press HOME twice to visit the home screen and return to Live TV.
+ // Press HOME twice to visit the home screen and return to TV app.
controller.pressHome();
// Wait until home screen is shown.
controller.waitForIdle();
diff --git a/tests/input/AndroidManifest.xml b/tests/input/AndroidManifest.xml
index fa52946..564323a 100644
--- a/tests/input/AndroidManifest.xml
+++ b/tests/input/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.testinput">
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<!-- Required to update or read existing channel and program information in TvProvider. -->
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputService.java b/tests/input/src/com/android/tv/testinput/TestTvInputService.java
index 840587c..d19f455 100644
--- a/tests/input/src/com/android/tv/testinput/TestTvInputService.java
+++ b/tests/input/src/com/android/tv/testinput/TestTvInputService.java
@@ -53,7 +53,7 @@
private static final int REFRESH_DELAY_MS = 1000 / 5;
private static final boolean DEBUG = false;
- // Consider the command delivering time from Live TV.
+ // Consider the command delivering time from TV app.
private static final long MAX_COMMAND_DELAY = TimeUnit.SECONDS.toMillis(3);
private final TestInputControl mBackend = TestInputControl.getInstance();
diff --git a/tests/jank/AndroidManifest.xml b/tests/jank/AndroidManifest.xml
index 7c0997a..1538851 100644
--- a/tests/jank/AndroidManifest.xml
+++ b/tests/jank/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.tests.jank" >
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23" />
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23" />
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/jank/src/com/android/tv/tests/jank/Utils.java b/tests/jank/src/com/android/tv/tests/jank/Utils.java
index 4ad0f64..57e5f10 100644
--- a/tests/jank/src/com/android/tv/tests/jank/Utils.java
+++ b/tests/jank/src/com/android/tv/tests/jank/Utils.java
@@ -19,7 +19,7 @@
import com.android.tv.testing.uihelper.UiDeviceUtils;
public final class Utils {
- /** Live TV process name */
+ /** TV app process name */
public static final String LIVE_CHANNELS_PROCESS_NAME = "com.android.tv";
private Utils() {}
diff --git a/tests/robotests/README.md b/tests/robotests/README.md
new file mode 100644
index 0000000..8e4bcb5
--- /dev/null
+++ b/tests/robotests/README.md
@@ -0,0 +1,5 @@
+Unit test suite for Live Channels using Robolectric.
+
+```
+$ m -j96 RunTvRoboTests
+```
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/tv/MainActivityRoboTest.java b/tests/robotests/src/com/android/tv/MainActivityRoboTest.java
new file mode 100644
index 0000000..357c163
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/MainActivityRoboTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2018 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 android.media.tv.TvTrackInfo;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import com.android.tv.common.flags.impl.DefaultLegacyFlags;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.StreamInfo;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.ui.TunableTvView;
+import com.android.tv.util.TvInputManagerHelper;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Tests for {@link TunableTvView} */
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ sdk = ConfigConstants.SDK,
+ application = TestSingletonApp.class,
+ shadows = {ShadowTvView.class})
+public class MainActivityRoboTest {
+ private ShadowTvView mShadowTvView;
+ private FakeMainActivity mMainActivity;
+
+ @Before
+ public void setUp() {
+ mMainActivity = Robolectric.buildActivity(FakeMainActivity.class).create().get();
+ mShadowTvView = Shadow.extract(mMainActivity.getTvView().getTvView());
+ mShadowTvView.listener = mMainActivity.getListener();
+ }
+
+ @Test
+ public void testSelectAudioTrack_autoSelect() {
+ mShadowTvView.mAudioTrackCountChanged = false;
+ setTracks(
+ TvTrackInfo.TYPE_AUDIO,
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "EN audio 1", "EN"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 1", "FR"));
+ mMainActivity.selectAudioTrack("FR audio 1");
+ assertThat(mMainActivity.getSelectedTrack(TvTrackInfo.TYPE_AUDIO)).isEqualTo("FR audio 1");
+
+ setTracks(
+ TvTrackInfo.TYPE_AUDIO,
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "EN audio 2", "EN"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 2", "FR"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 3", "FR"));
+ mMainActivity.applyMultiAudio(null);
+ // FR audio 2 is selected according the previously selected track.
+ assertThat(mMainActivity.getSelectedTrack(TvTrackInfo.TYPE_AUDIO)).isEqualTo("FR audio 2");
+ }
+
+ @Test
+ public void testSelectAudioTrack_audioTrackCountChanged() {
+ mShadowTvView.mAudioTrackCountChanged = true;
+ setTracks(
+ TvTrackInfo.TYPE_AUDIO,
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "EN audio 1", "EN"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 1", "FR"));
+ mMainActivity.selectAudioTrack("FR audio 1");
+ assertThat(mMainActivity.getSelectedTrack(TvTrackInfo.TYPE_AUDIO)).isEqualTo("FR audio 1");
+
+ setTracks(
+ TvTrackInfo.TYPE_AUDIO,
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "EN audio 2", "EN"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 2", "FR"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 3", "FR"));
+ mMainActivity.selectAudioTrack("FR audio 3");
+ // FR audio 3 is selected even if the track info has been changed
+ assertThat(mMainActivity.getSelectedTrack(TvTrackInfo.TYPE_AUDIO)).isEqualTo("FR audio 3");
+ }
+
+ @Test
+ public void testSelectAudioTrack_audioTrackCountNotChanged() {
+ mShadowTvView.mAudioTrackCountChanged = false;
+ setTracks(
+ TvTrackInfo.TYPE_AUDIO,
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "EN audio 1", "EN"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 1", "FR"));
+ mMainActivity.selectAudioTrack("FR audio 1");
+ assertThat(mMainActivity.getSelectedTrack(TvTrackInfo.TYPE_AUDIO)).isEqualTo("FR audio 1");
+
+ setTracks(
+ TvTrackInfo.TYPE_AUDIO,
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "EN audio 2", "EN"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 2", "FR"),
+ buildTrackForTesting(TvTrackInfo.TYPE_AUDIO, "FR audio 3", "FR"));
+ mMainActivity.selectAudioTrack("FR audio 3");
+ assertThat(mMainActivity.getSelectedTrack(TvTrackInfo.TYPE_AUDIO)).isEqualTo("FR audio 3");
+ }
+
+ private void setTracks(int type, TvTrackInfo... tracks) {
+ mShadowTvView.mTracks.put(type, Arrays.asList(tracks));
+ mShadowTvView.mSelectedTracks.put(type, null);
+ }
+
+ private TvTrackInfo buildTrackForTesting(int type, String id, String language) {
+ return new TvTrackInfo.Builder(type, id)
+ .setLanguage(language)
+ .setAudioChannelCount(0)
+ .build();
+ }
+
+ /** A {@link MainActivity} class for tests */
+ public static class FakeMainActivity extends MainActivity {
+ private MyOnTuneListener mListener;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Override onCreate() to omit unnecessary member variables
+ mTvView =
+ (TunableTvView)
+ LayoutInflater.from(RuntimeEnvironment.application)
+ .inflate(R.layout.activity_tv, null)
+ .findViewById(R.id.main_tunable_tv_view);
+ DefaultLegacyFlags legacyFlags = DefaultLegacyFlags.DEFAULT;
+ mTvView.initialize(
+ new ProgramDataManager(RuntimeEnvironment.application),
+ new TvInputManagerHelper(RuntimeEnvironment.application, legacyFlags),
+ legacyFlags);
+ mTvView.start();
+ mListener =
+ new MyOnTuneListener() {
+ @Override
+ public void onStreamInfoChanged(
+ StreamInfo info, boolean allowAutoSelectionOfTrack) {
+ applyMultiAudio(
+ allowAutoSelectionOfTrack
+ ? null
+ : getSelectedTrack(TvTrackInfo.TYPE_AUDIO));
+ }
+ };
+ mTvView.setOnTuneListener(mListener);
+ }
+
+ public TunableTvView getTvView() {
+ return mTvView;
+ }
+
+ public MyOnTuneListener getListener() {
+ return mListener;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/MediaSessionWrapperTest.java b/tests/robotests/src/com/android/tv/MediaSessionWrapperTest.java
new file mode 100644
index 0000000..1a2b2ca
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/MediaSessionWrapperTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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 android.app.PendingIntent;
+import android.content.Intent;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
+import com.android.tv.testing.EpgTestData;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.shadows.ShadowMediaSession;
+import com.google.common.collect.Maps;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Tests fpr {@link MediaSessionWrapper}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ sdk = ConfigConstants.SDK,
+ application = TestSingletonApp.class,
+ shadows = {ShadowMediaSession.class}
+)
+public class MediaSessionWrapperTest {
+
+ private static final int TEST_REQUEST_CODE = 1337;
+
+ private ShadowMediaSession mediaSession;
+ private MediaSessionWrapper mediaSessionWrapper;
+ private PendingIntent pendingIntent;
+
+ @Before
+ public void setUp() {
+ pendingIntent =
+ PendingIntent.getActivity(
+ RuntimeEnvironment.application, TEST_REQUEST_CODE, new Intent(), 0);
+ mediaSessionWrapper =
+ new MediaSessionWrapper(RuntimeEnvironment.application, pendingIntent) {
+ @Override
+ void initMediaController() {
+ // Not use MediaController for tests here because:
+ // 1. it's not allow to shadow MediaController
+ // 2. The Context TestSingletonApp is not an instance of Activity so
+ // Activity.setMediaController cannot be called.
+ // onPlaybackStateChanged() is called in #setPlaybackState instead.
+ }
+ @Override
+ void unregisterMediaControllerCallback() {
+ }
+ };
+ mediaSession = Shadow.extract(mediaSessionWrapper.getMediaSession());
+ }
+
+ @Test
+ public void setSessionActivity() {
+ assertThat(mediaSession.mSessionActivity).isEqualTo(this.pendingIntent);
+ }
+
+ @Test
+ public void setPlaybackState_true() {
+ setPlaybackState(true);
+ assertThat(mediaSession.mActive).isTrue();
+ assertThat(mediaSession.mPlaybackState.getState()).isEqualTo(PlaybackState.STATE_PLAYING);
+ }
+
+ @Test
+ public void setPlaybackState_false() {
+ setPlaybackState(false);
+ assertThat(mediaSession.mActive).isFalse();
+ assertThat(mediaSession.mPlaybackState).isNull();
+ }
+
+ @Test
+ public void setPlaybackState_trueThenFalse() {
+ setPlaybackState(true);
+ setPlaybackState(false);
+ assertThat(mediaSession.mActive).isFalse();
+ assertThat(mediaSession.mPlaybackState.getState()).isEqualTo(PlaybackState.STATE_STOPPED);
+ }
+
+ @Test
+ public void update_channel10() {
+
+ mediaSessionWrapper.update(false, EpgTestData.toTvChannel(EpgTestData.CHANNEL_10), null);
+ assertThat(asMap(mediaSession.mMediaMetadata))
+ .containsExactly(MediaMetadata.METADATA_KEY_TITLE, "Channel TEN");
+ }
+
+ @Test
+ public void update_blockedChannel10() {
+ mediaSessionWrapper.update(true, EpgTestData.toTvChannel(EpgTestData.CHANNEL_10), null);
+ assertThat(asMap(mediaSession.mMediaMetadata))
+ .containsExactly(
+ MediaMetadata.METADATA_KEY_TITLE,
+ "Channel blocked",
+ MediaMetadata.METADATA_KEY_ART,
+ null);
+ }
+
+ @Test
+ public void update_channel10Program2() {
+ mediaSessionWrapper.update(
+ false, EpgTestData.toTvChannel(EpgTestData.CHANNEL_10), EpgTestData.PROGRAM_2);
+ assertThat(asMap(mediaSession.mMediaMetadata))
+ .containsExactly(MediaMetadata.METADATA_KEY_TITLE, "Program 2");
+ }
+
+ @Test
+ public void update_blockedChannel10Program2() {
+ mediaSessionWrapper.update(
+ true, EpgTestData.toTvChannel(EpgTestData.CHANNEL_10), EpgTestData.PROGRAM_2);
+ assertThat(asMap(mediaSession.mMediaMetadata))
+ .containsExactly(
+ MediaMetadata.METADATA_KEY_TITLE,
+ "Channel blocked",
+ MediaMetadata.METADATA_KEY_ART,
+ null);
+ // TODO(b/70559407): test async loading of images.
+ }
+
+ @Test
+ public void release() {
+ mediaSessionWrapper.release();
+ assertThat(mediaSession.mReleased).isTrue();
+ }
+
+ private Map<String, Object> asMap(MediaMetadata mediaMetadata) {
+ return Maps.asMap(mediaMetadata.keySet(), key -> mediaMetadata.getString(key));
+ }
+
+ private void setPlaybackState(boolean isPlaying) {
+ mediaSessionWrapper.setPlaybackState(isPlaying);
+ mediaSessionWrapper.getMediaControllerCallback().onPlaybackStateChanged(
+ isPlaying
+ ? MediaSessionWrapper.MEDIA_SESSION_STATE_PLAYING
+ : MediaSessionWrapper.MEDIA_SESSION_STATE_STOPPED);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/SetupPassthroughActivityTest.java b/tests/robotests/src/com/android/tv/SetupPassthroughActivityTest.java
new file mode 100644
index 0000000..a442ec1
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/SetupPassthroughActivityTest.java
@@ -0,0 +1,446 @@
+/*
+ * 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.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.RuntimeEnvironment;
+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<SetupPassthroughActivity> activityController =
+ Robolectric.buildActivity(
+ SetupPassthroughActivity.class,
+ CommonUtils.createSetupIntent(new Intent(), testInput.getId()));
+ SetupPassthroughActivity activity = activityController.get();
+ ShadowActivity shadowActivity = shadowOf(activity);
+ shadowActivity.setCallingActivity(
+ new ComponentName(CommonConstants.BASE_PACKAGE, "com.example.MyClass"));
+ 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<SetupPassthroughActivity> 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 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(RuntimeEnvironment.application);
+ 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(RuntimeEnvironment.application);
+ testSingletonApp.setupUtils = setupUtils;
+ testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
+ testSingletonApp.getCloudEpgFlags().setThirdPartyEpgInputCsv(testInput.getId());
+ 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_notWhiteListed() {
+ TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest();
+ TestSetupUtils setupUtils = new TestSetupUtils(RuntimeEnvironment.application);
+ 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(RuntimeEnvironment.application);
+ testSingletonApp.setupUtils = setupUtils;
+ testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
+ testSingletonApp.getCloudEpgFlags().setThirdPartyEpgInputCsv(testInput.getId());
+ 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(RuntimeEnvironment.application);
+ 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) {
+ return Robolectric.buildActivity(
+ SetupPassthroughActivity.class,
+ CommonUtils.createSetupIntent(new Intent(), inputId))
+ .create()
+ .get();
+ }
+
+ 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;
+ }
+
+ /**
+ * Test SetupUtils.
+ *
+ * <p>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<Object> 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<Object> androidInjector() {
+ return dispatchingAndroidInjector;
+ }
+
+ protected AndroidInjector<MyTestApp> 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<MyTestApp> {}
+
+ @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<BuiltInTunerManager> 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;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/ShadowTvView.java b/tests/robotests/src/com/android/tv/ShadowTvView.java
new file mode 100644
index 0000000..8aad9f0
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/ShadowTvView.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 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 android.content.Context;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
+import android.util.AttributeSet;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowView;
+
+// TODO(b/78304522): move this class to robolectric
+/** Shadow class of {@link TvView}. */
+@Implements(TvView.class)
+public class ShadowTvView extends ShadowView {
+ public Map<Integer, String> mSelectedTracks = new HashMap<>();
+ public Map<Integer, List<TvTrackInfo>> mTracks = new HashMap<>();
+ public MainActivity.MyOnTuneListener listener;
+ public TvView.TvInputCallback mCallback;
+ public boolean mAudioTrackCountChanged;
+
+ @Implementation
+ public void __constructor__(Context context) {
+ }
+
+ @Implementation
+ public void __constructor__(Context context, AttributeSet attrs) {
+ }
+
+ @Override
+ public void __constructor__(Context context, AttributeSet attrs, int defStyleAttr) {
+ }
+
+ @Implementation
+ public List<TvTrackInfo> getTracks(int type) {
+ return mTracks.get(type);
+ }
+
+ @Implementation
+ public void selectTrack(int type, String trackId) {
+ mSelectedTracks.put(type, trackId);
+ int infoIndex = findTrackIndex(type, trackId);
+ // for some drivers, audio track count is set to 0 until the corresponding track is
+ // selected. Here we replace the track with another one whose audio track count is non-zero
+ // to test this case.
+ if (mAudioTrackCountChanged) {
+ replaceTrack(type, infoIndex);
+ }
+ mCallback.onTrackSelected("fakeInputId", type, trackId);
+ }
+
+ @Implementation
+ public String getSelectedTrack(int type) {
+ return mSelectedTracks.get(type);
+ }
+
+ @Implementation
+ public void setCallback(TvView.TvInputCallback callback) {
+ mCallback = callback;
+ }
+
+ private int findTrackIndex(int type, String trackId) {
+ List<TvTrackInfo> tracks = mTracks.get(type);
+ if (tracks == null) {
+ return -1;
+ }
+ for (int i = 0; i < tracks.size(); i++) {
+ TvTrackInfo info = tracks.get(i);
+ if (info.getId().equals(trackId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void replaceTrack(int type, int trackIndex) {
+ if (trackIndex >= 0) {
+ TvTrackInfo info = mTracks.get(type).get(trackIndex);
+ info = new TvTrackInfo
+ .Builder(info.getType(), info.getId())
+ .setLanguage(info.getLanguage())
+ .setAudioChannelCount(info.getAudioChannelCount() + 2)
+ .build();
+ mTracks.get(type).set(trackIndex, info);
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/TvActivityTest.java b/tests/robotests/src/com/android/tv/TvActivityTest.java
new file mode 100644
index 0000000..81160dd
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/TvActivityTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 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 android.content.Intent;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.util.Utils;
+import com.google.android.libraries.testing.truth.IntentSubject;
+import com.google.common.truth.Truth;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+/** Test for {@link TvActivity}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class TvActivityTest {
+
+ @Test
+ public void testLifeCycle() {
+ TvActivity activity = Robolectric.setupActivity(TvActivity.class);
+ Truth.assertThat(activity.isFinishing()).isTrue();
+
+ Intent nextStartedActivity = ShadowApplication.getInstance().getNextStartedActivity();
+ IntentSubject.assertThat(nextStartedActivity).hasComponentClass(MainActivity.class);
+ IntentSubject.assertThat(nextStartedActivity).hasExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, true);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/audio/AudioManagerHelperTest.java b/tests/robotests/src/com/android/tv/audio/AudioManagerHelperTest.java
new file mode 100644
index 0000000..e96e7df
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/audio/AudioManagerHelperTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2018 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.audio;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.media.AudioManager;
+import android.os.Build;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.ui.api.TunableTvViewPlayingApi;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link AudioManagerHelper}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class AudioManagerHelperTest {
+
+ private AudioManagerHelper mAudioManagerHelper;
+ private TestTvView mTvView;
+ private AudioManager mAudioManager;
+
+ @Before
+ public void setup() {
+ Activity testActivity = Robolectric.buildActivity(Activity.class).get();
+ mTvView = new TestTvView();
+ mAudioManager = RuntimeEnvironment.application.getSystemService(AudioManager.class);
+
+ mAudioManagerHelper = new AudioManagerHelper(testActivity, mTvView);
+ }
+
+ @Test
+ public void onAudioFocusChange_none_noTimeShift() {
+ mTvView.mTimeShiftAvailable = false;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_NONE);
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isZero();
+ }
+
+ @Test
+ public void onAudioFocusChange_none_TimeShift() {
+ mTvView.mTimeShiftAvailable = true;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_NONE);
+
+ assertThat(mTvView.mPaused).isTrue();
+ assertThat(mTvView.mVolume).isNull();
+ }
+
+ @Test
+ public void onAudioFocusChange_gain_noTimeShift() {
+ mTvView.mTimeShiftAvailable = false;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN);
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isEqualTo(1.0f);
+ }
+
+ @Test
+ public void onAudioFocusChange_gain_timeShift() {
+ mTvView.mTimeShiftAvailable = true;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN);
+
+ assertThat(mTvView.mPaused).isFalse();
+ assertThat(mTvView.mVolume).isNull();
+ }
+
+ @Test
+ public void onAudioFocusChange_loss_noTimeShift() {
+ mTvView.mTimeShiftAvailable = false;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS);
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isEqualTo(0.0f);
+ }
+
+ @Test
+ public void onAudioFocusChange_loss_timeShift() {
+ mTvView.mTimeShiftAvailable = true;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS);
+
+ assertThat(mTvView.mPaused).isTrue();
+ assertThat(mTvView.mVolume).isNull();
+ }
+
+ @Test
+ public void onAudioFocusChange_lossTransient_noTimeShift() {
+ mTvView.mTimeShiftAvailable = false;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isEqualTo(0.0f);
+ }
+
+ @Test
+ public void onAudioFocusChange_lossTransient_timeShift() {
+ mTvView.mTimeShiftAvailable = true;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+
+ assertThat(mTvView.mPaused).isTrue();
+ assertThat(mTvView.mVolume).isNull();
+ }
+
+ @Test
+ public void onAudioFocusChange_lossTransientCanDuck_noTimeShift() {
+ mTvView.mTimeShiftAvailable = false;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isEqualTo(0.3f);
+ }
+
+ @Test
+ public void onAudioFocusChange_lossTransientCanDuck_timeShift() {
+ mTvView.mTimeShiftAvailable = true;
+
+ mAudioManagerHelper.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
+
+ assertThat(mTvView.mPaused).isTrue();
+ assertThat(mTvView.mVolume).isNull();
+ }
+
+ @Test
+ @Config(sdk = {ConfigConstants.SDK, Build.VERSION_CODES.O})
+ public void requestAudioFocus_granted() {
+ Shadows.shadowOf(mAudioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ mAudioManagerHelper.requestAudioFocus();
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isEqualTo(1.0f);
+ }
+
+ @Test
+ @Config(sdk = {ConfigConstants.SDK, Build.VERSION_CODES.O})
+ public void requestAudioFocus_failed() {
+ Shadows.shadowOf(mAudioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_FAILED);
+
+ mAudioManagerHelper.requestAudioFocus();
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isZero();
+ }
+
+ @Test
+ @Config(sdk = {ConfigConstants.SDK, Build.VERSION_CODES.O})
+ public void requestAudioFocus_delayed() {
+ Shadows.shadowOf(mAudioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_DELAYED);
+
+ mAudioManagerHelper.requestAudioFocus();
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isZero();
+ }
+
+ @Test
+ public void setVolumeByAudioFocusStatus_started() {
+ mAudioManagerHelper.setVolumeByAudioFocusStatus();
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isZero();
+ }
+
+ @Test
+ public void setVolumeByAudioFocusStatus_notStarted() {
+ mTvView.mStarted = false;
+ mAudioManagerHelper.setVolumeByAudioFocusStatus();
+
+ assertThat(mTvView.mPaused).isNull();
+ assertThat(mTvView.mVolume).isNull();
+ }
+
+ private static class TestTvView implements TunableTvViewPlayingApi {
+ private boolean mStarted = true;
+ private boolean mTimeShiftAvailable = false;
+ private Float mVolume = null;
+ private Boolean mPaused = null;
+
+ @Override
+ public boolean isPlaying() {
+ return mStarted;
+ }
+
+ @Override
+ public void setStreamVolume(float volume) {
+ mVolume = volume;
+ }
+
+ @Override
+ public void setTimeShiftListener(TimeShiftListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isTimeShiftAvailable() {
+ return mTimeShiftAvailable;
+ }
+
+ @Override
+ public void timeShiftPlay() {
+ mPaused = false;
+ }
+
+ @Override
+ public void timeShiftPause() {
+ mPaused = true;
+ }
+
+ @Override
+ public void timeShiftRewind(int speed) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void timeShiftFastForward(int speed) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void timeShiftSeekTo(long timeMs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long timeShiftGetCurrentPositionMs() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/ChannelNumberTest.java b/tests/robotests/src/com/android/tv/data/ChannelNumberTest.java
new file mode 100644
index 0000000..14c9bc4
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/ChannelNumberTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.data;
+
+import static com.android.tv.data.ChannelNumber.parseChannelNumber;
+import static com.android.tv.testing.ChannelNumberSubject.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.testing.ComparableTester;
+import com.android.tv.testing.constants.ConfigConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link ChannelNumber}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class ChannelNumberTest {
+
+ @Test
+ public void newChannelNumber() {
+ assertThat(new ChannelNumber()).isEmpty();
+ }
+
+ @Test
+ public void parseChannelNumber_empty() {
+ assertThat(parseChannelNumber("")).isNull();
+ }
+
+ @Test
+ public void parseChannelNumber_dash() {
+ assertThat(parseChannelNumber("-")).isNull();
+ }
+
+ @Test
+ public void parseChannelNumber_abcd12() {
+ assertThat(parseChannelNumber("abcd12")).isNull();
+ }
+
+ @Test
+ public void parseChannelNumber_12abcd() {
+ assertThat(parseChannelNumber("12abcd")).isNull();
+ }
+
+ @Test
+ public void parseChannelNumber_dash12() {
+ assertThat(parseChannelNumber("-12")).isNull();
+ }
+
+ @Test
+ public void parseChannelNumber_1() {
+ assertThat(parseChannelNumber("1")).displaysAs(1);
+ }
+
+ @Test
+ public void parseChannelNumber_1234dash4321() {
+ assertThat(parseChannelNumber("1234-4321")).displaysAs(1234, 4321);
+ }
+
+ @Test
+ public void parseChannelNumber_3dash4() {
+ assertThat(parseChannelNumber("3-4")).displaysAs(3, 4);
+ }
+
+ @Test
+ public void parseChannelNumber_5dash6() {
+ assertThat(parseChannelNumber("5-6")).displaysAs(5, 6);
+ }
+
+ @Test
+ public void compareTo() {
+ new ComparableTester<ChannelNumber>()
+ .addEquivalentGroup(parseChannelNumber("1"), parseChannelNumber("1"))
+ .addEquivalentGroup(parseChannelNumber("2"))
+ .addEquivalentGroup(parseChannelNumber("2-1"))
+ .addEquivalentGroup(parseChannelNumber("2-2"))
+ .addEquivalentGroup(parseChannelNumber("2-10"))
+ .addEquivalentGroup(parseChannelNumber("3"))
+ .addEquivalentGroup(parseChannelNumber("4"), parseChannelNumber("4-0"))
+ .addEquivalentGroup(parseChannelNumber("10"))
+ .addEquivalentGroup(parseChannelNumber("100"))
+ .test();
+ }
+
+ @Test
+ public void compare_null_null() {
+ assertThat(ChannelNumber.compare(null, null)).isEqualTo(0);
+ }
+
+ @Test
+ public void compare_1_1() {
+ assertThat(ChannelNumber.compare("1", "1")).isEqualTo(0);
+ ;
+ }
+
+ @Test
+ public void compare_null_1() {
+ assertThat(ChannelNumber.compare(null, "1")).isLessThan(0);
+ }
+
+ @Test
+ public void compare_abcd_1() {
+ assertThat(ChannelNumber.compare("abcd", "1")).isLessThan(0);
+ }
+
+ @Test
+ public void compare_dash1_1() {
+ assertThat(ChannelNumber.compare(".4", "1")).isLessThan(0);
+ }
+
+ @Test
+ public void compare_1_null() {
+ assertThat(ChannelNumber.compare("1", null)).isGreaterThan(0);
+ }
+
+ @Test
+ public void equivalent_null_to_null() {
+ assertThat(ChannelNumber.equivalent(null, null)).isTrue();
+ }
+
+ @Test
+ public void equivalent_1_to_1() {
+ assertThat(ChannelNumber.equivalent("1", "1")).isTrue();
+ }
+
+ @Test
+ public void equivalent_1d2_to_1() {
+ assertThat(ChannelNumber.equivalent("1-2", "1")).isTrue();
+ }
+
+ @Test
+ public void equivalent_1_to_1d2() {
+ assertThat(ChannelNumber.equivalent("1", "1-2")).isTrue();
+ }
+
+ @Test
+ public void equivalent_1_to_2_isFalse() {
+ assertThat(ChannelNumber.equivalent("1", "2")).isFalse();
+ }
+
+ @Test
+ public void equivalent_1d1_to_1d1() {
+ assertThat(ChannelNumber.equivalent("1-1", "1-1")).isTrue();
+ }
+
+ @Test
+ public void equivalent_1d1_to_1d2_isFalse() {
+ assertThat(ChannelNumber.equivalent("1-1", "1-2")).isFalse();
+ }
+
+ @Test
+ public void equivalent_1_to_null_isFalse() {
+ assertThat(ChannelNumber.equivalent("1", null)).isFalse();
+ }
+
+ @Test
+ public void equivalent_null_to_1_isFalse() {
+ assertThat(ChannelNumber.equivalent(null, "1")).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/GenreItemTest.java b/tests/robotests/src/com/android/tv/data/GenreItemTest.java
new file mode 100644
index 0000000..c7adce2
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/GenreItemTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.tv.TvContract.Programs.Genres;
+import android.os.Build;
+import com.android.tv.testing.constants.ConfigConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link GenreItems}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class GenreItemTest {
+ private static final String INVALID_GENRE = "INVALID GENRE";
+
+ @Test
+ public void testGetLabels() {
+ // Checks if no exception is thrown.
+ GenreItems.getLabels(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void testGetCanonicalGenre() {
+ int count = GenreItems.getGenreCount();
+ assertThat(GenreItems.getCanonicalGenre(GenreItems.ID_ALL_CHANNELS)).isNull();
+ for (int i = 1; i < count; ++i) {
+ assertThat(GenreItems.getCanonicalGenre(i)).isNotNull();
+ }
+ }
+
+ @Test
+ public void testGetId_base() {
+ int count = GenreItems.getGenreCount();
+ assertThat(GenreItems.getId(null)).isEqualTo(GenreItems.ID_ALL_CHANNELS);
+ assertThat(GenreItems.getId(INVALID_GENRE)).isEqualTo(GenreItems.ID_ALL_CHANNELS);
+ assertInRange(GenreItems.getId(Genres.FAMILY_KIDS), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.SPORTS), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.SHOPPING), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.MOVIES), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.COMEDY), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.TRAVEL), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.DRAMA), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.EDUCATION), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.ANIMAL_WILDLIFE), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.NEWS), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.GAMING), 1, count - 1);
+ }
+
+ @Test
+ public void testGetId_lmp_mr1() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
+ assertThat(GenreItems.getId(Genres.ARTS)).isEqualTo(GenreItems.ID_ALL_CHANNELS);
+ assertThat(GenreItems.getId(Genres.ENTERTAINMENT))
+ .isEqualTo(GenreItems.ID_ALL_CHANNELS);
+ assertThat(GenreItems.getId(Genres.LIFE_STYLE)).isEqualTo(GenreItems.ID_ALL_CHANNELS);
+ assertThat(GenreItems.getId(Genres.MUSIC)).isEqualTo(GenreItems.ID_ALL_CHANNELS);
+ assertThat(GenreItems.getId(Genres.PREMIER)).isEqualTo(GenreItems.ID_ALL_CHANNELS);
+ assertThat(GenreItems.getId(Genres.TECH_SCIENCE)).isEqualTo(GenreItems.ID_ALL_CHANNELS);
+ } else {
+ int count = GenreItems.getGenreCount();
+ assertInRange(GenreItems.getId(Genres.ARTS), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.ENTERTAINMENT), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.LIFE_STYLE), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.MUSIC), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.PREMIER), 1, count - 1);
+ assertInRange(GenreItems.getId(Genres.TECH_SCIENCE), 1, count - 1);
+ }
+ }
+
+ private void assertInRange(int value, int lower, int upper) {
+ assertThat(value >= lower && value <= upper).isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/PreviewDataManagerTest.java b/tests/robotests/src/com/android/tv/data/PreviewDataManagerTest.java
new file mode 100644
index 0000000..cfbf315
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/PreviewDataManagerTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.tvprovider.media.tv.PreviewProgram;
+import androidx.tvprovider.media.tv.TvContractCompat;
+
+import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.testing.constants.ConfigConstants;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowLog;
+
+import java.util.List;
+
+/** Tests for {@link PreviewDataManager}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class PreviewDataManagerTest {
+ private static final long FAKE_PREVIEW_CHANNEL_ID = 2002;
+ private static final long FAKE_PROGRAM_ID = 1001;
+ private static final String FAKE_PROGRAM_TITLE = "test program";
+ private static final String FAKE_PROGRAM_POSTER_URI = "http://fake.uri/poster.jpg";
+ private static final long FAKE_CHANNEL_ID = 1002;
+ private static final String FAKE_CHANNEL_DISPLAY_NAME = "test channel";
+ private static final String FAKE_INPUT_ID = "test input";
+
+ private static class QueryExceptionProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public Cursor query(
+ @NonNull Uri uri,
+ @Nullable String[] projection,
+ @Nullable String selection,
+ @Nullable String[] selectionArgs,
+ @Nullable String sortOrder) {
+ throw new SQLException("Testing " + uri);
+ }
+
+ @Nullable
+ @Override
+ public String getType(@NonNull Uri uri) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(
+ @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(
+ @NonNull Uri uri,
+ @Nullable ContentValues values,
+ @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ return 0;
+ }
+ }
+
+ @Test
+ public void start() {
+ PreviewDataManager previewDataManager =
+ new PreviewDataManager(RuntimeEnvironment.application);
+ assertThat(previewDataManager.isLoadFinished()).isFalse();
+ previewDataManager.start();
+ assertThat(previewDataManager.isLoadFinished()).isTrue();
+ }
+
+ @Test
+ public void queryPreviewData_sqlexception() {
+ ProviderInfo info = new ProviderInfo();
+ info.authority = TvContractCompat.AUTHORITY;
+ QueryExceptionProvider provider =
+ Robolectric.buildContentProvider(QueryExceptionProvider.class).create(info).get();
+ ShadowContentResolver.registerProviderInternal(TvContractCompat.AUTHORITY, provider);
+
+ PreviewDataManager previewDataManager =
+ new PreviewDataManager(RuntimeEnvironment.application);
+ assertThat(previewDataManager.isLoadFinished()).isFalse();
+ previewDataManager.start();
+ List<ShadowLog.LogItem> logs = ShadowLog.getLogsForTag("PreviewDataManager");
+ // The only warning should be the test one
+ // NOTE: I am not using hamcrest matchers because of weird class loading issues
+ // TODO: use truth
+ for (ShadowLog.LogItem log : logs) {
+ if (log.type == Log.WARN) {
+ assertThat(log.msg).isEqualTo("Unable to get preview data");
+ assertThat(log.throwable).isInstanceOf(SQLException.class);
+ assertThat(log.throwable)
+ .hasMessageThat()
+ .isEqualTo("Testing content://android.media.tv/channel?preview=true");
+ }
+ }
+ }
+
+ @Test
+ public void createPreviewProgram_fromProgram() {
+ Program program =
+ new ProgramImpl.Builder()
+ .setId(FAKE_PROGRAM_ID)
+ .setTitle(FAKE_PROGRAM_TITLE)
+ .setPosterArtUri(FAKE_PROGRAM_POSTER_URI)
+ .build();
+ Channel channel =
+ new ChannelImpl.Builder()
+ .setId(FAKE_CHANNEL_ID)
+ .setDisplayName(FAKE_CHANNEL_DISPLAY_NAME)
+ .setInputId(FAKE_INPUT_ID)
+ .build();
+
+ PreviewProgram previewProgram =
+ PreviewDataManager.PreviewDataUtils.createPreviewProgramFromContent(
+ PreviewProgramContent.createFromProgram(
+ FAKE_PREVIEW_CHANNEL_ID, program, channel),
+ 0);
+
+ assertThat(previewProgram.getChannelId()).isEqualTo(FAKE_PREVIEW_CHANNEL_ID);
+ assertThat(previewProgram.getType())
+ .isEqualTo(TvContractCompat.PreviewPrograms.TYPE_CHANNEL);
+ assertThat(previewProgram.isLive()).isTrue();
+ assertThat(previewProgram.getTitle()).isEqualTo(FAKE_PROGRAM_TITLE);
+ assertThat(previewProgram.getDescription()).isEqualTo(FAKE_CHANNEL_DISPLAY_NAME);
+ assertThat(previewProgram.getPosterArtUri().toString()).isEqualTo(FAKE_PROGRAM_POSTER_URI);
+ assertThat(previewProgram.getIntentUri()).isEqualTo(channel.getUri());
+ assertThat(previewProgram.getPreviewVideoUri())
+ .isEqualTo(
+ PreviewDataManager.PreviewDataUtils.addQueryParamToUri(
+ channel.getUri(),
+ Pair.create(PreviewProgramContent.PARAM_INPUT, FAKE_INPUT_ID)));
+ assertThat(previewProgram.getInternalProviderId())
+ .isEqualTo(Long.toString(FAKE_PROGRAM_ID));
+ assertThat(previewProgram.getContentId()).isEqualTo(channel.getUri().toString());
+ }
+
+ @Test
+ public void createPreviewProgram_fromRecordedProgram() {
+ RecordedProgram program =
+ RecordedProgram.builder()
+ .setId(FAKE_PROGRAM_ID)
+ .setTitle(FAKE_PROGRAM_TITLE)
+ .setPosterArtUri(FAKE_PROGRAM_POSTER_URI)
+ .setInputId(FAKE_INPUT_ID)
+ .build();
+ Uri recordedProgramUri = TvContractCompat.buildRecordedProgramUri(FAKE_PROGRAM_ID);
+
+ PreviewProgram previewProgram =
+ PreviewDataManager.PreviewDataUtils.createPreviewProgramFromContent(
+ PreviewProgramContent.createFromRecordedProgram(
+ FAKE_PREVIEW_CHANNEL_ID, program, null),
+ 0);
+
+ assertThat(previewProgram.getChannelId()).isEqualTo(FAKE_PREVIEW_CHANNEL_ID);
+ assertThat(previewProgram.getType()).isEqualTo(TvContractCompat.PreviewPrograms.TYPE_CLIP);
+ assertThat(previewProgram.isLive()).isFalse();
+ assertThat(previewProgram.getTitle()).isEqualTo(FAKE_PROGRAM_TITLE);
+ assertThat(previewProgram.getDescription()).isEmpty();
+ assertThat(previewProgram.getPosterArtUri().toString()).isEqualTo(FAKE_PROGRAM_POSTER_URI);
+ assertThat(previewProgram.getIntentUri()).isEqualTo(recordedProgramUri);
+ assertThat(previewProgram.getPreviewVideoUri())
+ .isEqualTo(
+ PreviewDataManager.PreviewDataUtils.addQueryParamToUri(
+ recordedProgramUri,
+ Pair.create(PreviewProgramContent.PARAM_INPUT, FAKE_INPUT_ID)));
+ assertThat(previewProgram.getContentId()).isEqualTo(recordedProgramUri.toString());
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/ProgramDataManagerTest.java b/tests/robotests/src/com/android/tv/data/ProgramDataManagerTest.java
new file mode 100644
index 0000000..2176aa9
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/ProgramDataManagerTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import static android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ContentResolver;
+import android.media.tv.TvContract;
+
+import com.android.tv.common.flags.impl.DefaultBackendKnobsFlags;
+import com.android.tv.data.api.Program;
+import com.android.tv.perf.stub.StubPerformanceMonitor;
+import com.android.tv.testing.FakeTvInputManagerHelper;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.constants.Constants;
+import com.android.tv.testing.data.ProgramInfo;
+import com.android.tv.testing.data.ProgramUtils;
+import com.android.tv.testing.fakes.FakeClock;
+import com.android.tv.testing.fakes.FakeTvProvider;
+import com.android.tv.testing.robo.ContentProviders;
+import com.android.tv.testing.testdata.TestData;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.util.concurrent.RoboExecutorService;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** Test for {@link ProgramDataManager} */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
+public class ProgramDataManagerTest {
+
+ // Wait time for expected success.
+ private static final long WAIT_TIME_OUT_MS = 1000L;
+ // Wait time for expected failure.
+ private static final long FAILURE_TIME_OUT_MS = 300L;
+
+ private ProgramDataManager mProgramDataManager;
+ private FakeClock mClock;
+ private TestProgramDataManagerCallback mCallback;
+
+ @Before
+ public void setUp() {
+ mClock = FakeClock.createWithCurrentTime();
+ mCallback = new TestProgramDataManagerCallback();
+ ContentProviders.register(FakeTvProvider.class, TvContract.AUTHORITY);
+ TestData.DEFAULT_10_CHANNELS.init(
+ RuntimeEnvironment.application, mClock, TimeUnit.DAYS.toMillis(1));
+ FakeTvInputManagerHelper tvInputManagerHelper =
+ new FakeTvInputManagerHelper(RuntimeEnvironment.application);
+ RoboExecutorService executor = new RoboExecutorService();
+ ContentResolver contentResolver = RuntimeEnvironment.application.getContentResolver();
+ ChannelDataManager channelDataManager =
+ new ChannelDataManager(
+ RuntimeEnvironment.application,
+ tvInputManagerHelper,
+ executor,
+ contentResolver);
+ mProgramDataManager =
+ new ProgramDataManager(
+ RuntimeEnvironment.application,
+ executor,
+ RuntimeEnvironment.application.getContentResolver(),
+ mClock,
+ getMainLooper(),
+ new DefaultBackendKnobsFlags(),
+ new StubPerformanceMonitor(),
+ channelDataManager,
+ tvInputManagerHelper);
+
+ mProgramDataManager.setPrefetchEnabled(true);
+ mProgramDataManager.addCallback(mCallback);
+ }
+
+ @After
+ public void tearDown() {
+ mProgramDataManager.stop();
+ }
+
+ private void startAndWaitForComplete() throws InterruptedException {
+ mProgramDataManager.start();
+ shadowOf(getMainLooper()).idle();
+ assertThat(mCallback.channelUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS))
+ .isTrue();
+ }
+
+ /** Test for {@link ProgramInfo#getIndex} and {@link ProgramInfo#getStartTimeMs}. */
+ @Test
+ public void testProgramUtils() {
+ ProgramInfo stub = ProgramInfo.create();
+ for (long channelId = 1; channelId < Constants.UNIT_TEST_CHANNEL_COUNT; channelId++) {
+ int index = stub.getIndex(mClock.currentTimeMillis(), channelId);
+ long startTimeMs = stub.getStartTimeMs(index, channelId);
+ ProgramInfo programAt = stub.build(RuntimeEnvironment.application, index);
+ assertThat(startTimeMs).isAtMost(mClock.currentTimeMillis());
+ assertThat(mClock.currentTimeMillis()).isLessThan(startTimeMs + programAt.durationMs);
+ }
+ }
+
+ /**
+ * Test for following methods.
+ *
+ * <p>{@link ProgramDataManager#getCurrentProgram(long)}, {@link
+ * ProgramDataManager#getPrograms(long, long)}, {@link
+ * ProgramDataManager#setPrefetchTimeRange(long)}.
+ */
+ @Test
+ public void testGetPrograms() throws InterruptedException {
+ // Initial setup to test {@link ProgramDataManager#setPrefetchTimeRange(long)}.
+ long preventSnapDelayMs = ProgramDataManager.PROGRAM_GUIDE_SNAP_TIME_MS * 2;
+ long prefetchTimeRangeStartMs = System.currentTimeMillis() + preventSnapDelayMs;
+ mClock.setCurrentTimeMillis(prefetchTimeRangeStartMs + preventSnapDelayMs);
+ mProgramDataManager.setPrefetchTimeRange(prefetchTimeRangeStartMs);
+
+ startAndWaitForComplete();
+
+ for (long channelId = 1; channelId <= Constants.UNIT_TEST_CHANNEL_COUNT; channelId++) {
+ Program currentProgram = mProgramDataManager.getCurrentProgram(channelId);
+ // Test {@link ProgramDataManager#getCurrentProgram(long)}.
+ assertThat(currentProgram).isNotNull();
+ assertWithMessage("currentProgramStartTime")
+ .that(currentProgram.getStartTimeUtcMillis())
+ .isLessThan(mClock.currentTimeMillis());
+ assertWithMessage("currentProgramEndTime")
+ .that(currentProgram.getEndTimeUtcMillis())
+ .isGreaterThan(mClock.currentTimeMillis());
+
+ // Test {@link ProgramDataManager#getPrograms(long)}.
+ // Case #1: Normal case
+ List<Program> programs =
+ mProgramDataManager.getPrograms(channelId, mClock.currentTimeMillis());
+ ProgramInfo stub = ProgramInfo.create();
+ int index = stub.getIndex(mClock.currentTimeMillis(), channelId);
+ for (Program program : programs) {
+ ProgramInfo programInfoAt = stub.build(RuntimeEnvironment.application, index);
+ long startTimeMs = stub.getStartTimeMs(index, channelId);
+ assertProgramEquals(startTimeMs, programInfoAt, program);
+ index++;
+ }
+ // Case #2: Corner cases where there's a program that starts at the start of the range.
+ long startTimeMs = programs.get(0).getStartTimeUtcMillis();
+ programs = mProgramDataManager.getPrograms(channelId, startTimeMs);
+ assertThat(programs.get(0).getStartTimeUtcMillis()).isEqualTo(startTimeMs);
+
+ // Test {@link ProgramDataManager#setPrefetchTimeRange(long)}.
+ programs =
+ mProgramDataManager.getPrograms(
+ channelId, prefetchTimeRangeStartMs - TimeUnit.HOURS.toMillis(1));
+ for (Program program : programs) {
+ assertThat(program.getEndTimeUtcMillis()).isAtLeast(prefetchTimeRangeStartMs);
+ }
+ }
+ }
+
+ /**
+ * Test for following methods.
+ *
+ * <p>{@link ProgramDataManager#addOnCurrentProgramUpdatedListener}, {@link
+ * ProgramDataManager#removeOnCurrentProgramUpdatedListener}.
+ */
+ @Test
+ public void testCurrentProgramListener() throws InterruptedException {
+ final long testChannelId = 1;
+ ProgramInfo stub = ProgramInfo.create();
+ int index = stub.getIndex(mClock.currentTimeMillis(), testChannelId);
+ // Set current time to few seconds before the current program ends,
+ // so we can see if callback is called as expected.
+ long nextProgramStartTimeMs = stub.getStartTimeMs(index + 1, testChannelId);
+ ProgramInfo nextProgramInfo = stub.build(RuntimeEnvironment.application, index + 1);
+ mClock.setCurrentTimeMillis(nextProgramStartTimeMs - (WAIT_TIME_OUT_MS / 2));
+
+ startAndWaitForComplete();
+ // Note that changing current time doesn't affect the current program
+ // because current program is updated after waiting for the program's duration.
+ // See {@link ProgramDataManager#updateCurrentProgram}.
+ TestProgramDataManagerOnCurrentProgramUpdatedListener listener =
+ new TestProgramDataManagerOnCurrentProgramUpdatedListener();
+ mClock.setCurrentTimeMillis(mClock.currentTimeMillis() + WAIT_TIME_OUT_MS);
+ mProgramDataManager.addOnCurrentProgramUpdatedListener(testChannelId, listener);
+ shadowOf(getMainLooper()).runToEndOfTasks();
+ assertThat(
+ listener.currentProgramUpdatedLatch.await(
+ WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS))
+ .isTrue();
+ assertThat(listener.updatedChannelId).isEqualTo(testChannelId);
+ Program currentProgram = mProgramDataManager.getCurrentProgram(testChannelId);
+ assertProgramEquals(nextProgramStartTimeMs, nextProgramInfo, currentProgram);
+ assertThat(currentProgram).isEqualTo(listener.updatedProgram);
+ }
+
+ /** Test if program data is refreshed after the program insertion. */
+ @Test
+ public void testContentProviderUpdate() throws InterruptedException {
+ final long testChannelId = 1;
+ startAndWaitForComplete();
+ // Force program data manager to update program data whenever it's changes.
+ mProgramDataManager.setProgramPrefetchUpdateWait(0);
+ mCallback.reset();
+ List<Program> programList =
+ mProgramDataManager.getPrograms(testChannelId, mClock.currentTimeMillis());
+ assertThat(programList).isNotNull();
+ long lastProgramEndTime = programList.get(programList.size() - 1).getEndTimeUtcMillis();
+ // Make change in content provider
+ ProgramUtils.populatePrograms(
+ RuntimeEnvironment.application,
+ TvContract.buildChannelUri(testChannelId),
+ ProgramInfo.create(),
+ mClock,
+ TimeUnit.DAYS.toMillis(2));
+ shadowOf(getMainLooper()).runToEndOfTasks();
+ assertThat(mCallback.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS))
+ .isTrue();
+ programList = mProgramDataManager.getPrograms(testChannelId, mClock.currentTimeMillis());
+ assertThat(lastProgramEndTime)
+ .isLessThan(programList.get(programList.size() - 1).getEndTimeUtcMillis());
+ }
+
+ /** Test for {@link ProgramDataManager#setPauseProgramUpdate(boolean)}. */
+ @Test
+ public void testSetPauseProgramUpdate() throws InterruptedException {
+ final long testChannelId = 1;
+ startAndWaitForComplete();
+ // Force program data manager to update program data whenever it's changes.
+ mProgramDataManager.setProgramPrefetchUpdateWait(0);
+ mCallback.reset();
+ mProgramDataManager.setPauseProgramUpdate(true);
+ ProgramUtils.populatePrograms(
+ RuntimeEnvironment.application,
+ TvContract.buildChannelUri(testChannelId),
+ ProgramInfo.create(),
+ mClock,
+ TimeUnit.DAYS.toMillis(2));
+ shadowOf(getMainLooper()).runToEndOfTasks();
+ assertThat(mCallback.programUpdatedLatch.await(FAILURE_TIME_OUT_MS, TimeUnit.MILLISECONDS))
+ .isFalse();
+ }
+
+ public static void assertProgramEquals(
+ long expectedStartTime, ProgramInfo expectedInfo, Program actualProgram) {
+ assertWithMessage("title").that(actualProgram.getTitle()).isEqualTo(expectedInfo.title);
+ assertWithMessage("episode")
+ .that(actualProgram.getEpisodeTitle())
+ .isEqualTo(expectedInfo.episode);
+ assertWithMessage("description")
+ .that(actualProgram.getDescription())
+ .isEqualTo(expectedInfo.description);
+ assertWithMessage("startTime")
+ .that(actualProgram.getStartTimeUtcMillis())
+ .isEqualTo(expectedStartTime);
+ assertWithMessage("endTime")
+ .that(actualProgram.getEndTimeUtcMillis())
+ .isEqualTo(expectedStartTime + expectedInfo.durationMs);
+ }
+
+ private static class TestProgramDataManagerCallback implements ProgramDataManager.Callback {
+ public CountDownLatch programUpdatedLatch = new CountDownLatch(1);
+ public CountDownLatch channelUpdatedLatch = new CountDownLatch(1);
+
+ @Override
+ public void onProgramUpdated() {
+ programUpdatedLatch.countDown();
+ }
+
+ @Override
+ public void onChannelUpdated() {
+ channelUpdatedLatch.countDown();
+ }
+
+ public void reset() {
+ programUpdatedLatch = new CountDownLatch(1);
+ channelUpdatedLatch = new CountDownLatch(1);
+ }
+ }
+
+ private static class TestProgramDataManagerOnCurrentProgramUpdatedListener
+ implements OnCurrentProgramUpdatedListener {
+ public final CountDownLatch currentProgramUpdatedLatch = new CountDownLatch(1);
+ public long updatedChannelId = -1;
+ public Program updatedProgram = null;
+
+ @Override
+ public void onCurrentProgramUpdated(long channelId, Program program) {
+ updatedChannelId = channelId;
+ updatedProgram = program;
+ currentProgramUpdatedLatch.countDown();
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/ProgramTest.java b/tests/robotests/src/com/android/tv/data/ProgramTest.java
new file mode 100644
index 0000000..4f0c889
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/ProgramTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import static android.media.tv.TvContract.Programs.Genres.COMEDY;
+import static android.media.tv.TvContract.Programs.Genres.FAMILY_KIDS;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract.Programs.Genres;
+import android.os.Parcel;
+
+import com.android.tv.data.api.Program;
+import com.android.tv.data.api.Program.CriticScore;
+import com.android.tv.testing.constants.ConfigConstants;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Tests for {@link ProgramImpl}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class ProgramTest {
+ private static final int NOT_FOUND_GENRE = 987;
+
+ private static final int FAMILY_GENRE_ID = GenreItems.getId(FAMILY_KIDS);
+
+ private static final int COMEDY_GENRE_ID = GenreItems.getId(COMEDY);
+
+ @Test
+ public void testBuild() {
+ Program program = new ProgramImpl.Builder().build();
+ assertWithMessage("isValid").that(program.isValid()).isFalse();
+ }
+
+ @Test
+ public void testNoGenres() {
+ Program program = new ProgramImpl.Builder().setCanonicalGenres("").build();
+ assertNullCanonicalGenres(program);
+ assertHasGenre(program, NOT_FOUND_GENRE, false);
+ assertHasGenre(program, FAMILY_GENRE_ID, false);
+ assertHasGenre(program, COMEDY_GENRE_ID, false);
+ assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true);
+ }
+
+ @Test
+ public void testFamilyGenre() {
+ Program program = new ProgramImpl.Builder().setCanonicalGenres(FAMILY_KIDS).build();
+ assertCanonicalGenres(program, FAMILY_KIDS);
+ assertHasGenre(program, NOT_FOUND_GENRE, false);
+ assertHasGenre(program, FAMILY_GENRE_ID, true);
+ assertHasGenre(program, COMEDY_GENRE_ID, false);
+ assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true);
+ }
+
+ @Test
+ public void testFamilyComedyGenre() {
+ Program program =
+ new ProgramImpl.Builder().setCanonicalGenres(FAMILY_KIDS + ", " + COMEDY).build();
+ assertCanonicalGenres(program, FAMILY_KIDS, COMEDY);
+ assertHasGenre(program, NOT_FOUND_GENRE, false);
+ assertHasGenre(program, FAMILY_GENRE_ID, true);
+ assertHasGenre(program, COMEDY_GENRE_ID, true);
+ assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true);
+ }
+
+ @Test
+ public void testOtherGenre() {
+ Program program = new ProgramImpl.Builder().setCanonicalGenres("other").build();
+ assertCanonicalGenres(program);
+ assertHasGenre(program, NOT_FOUND_GENRE, false);
+ assertHasGenre(program, FAMILY_GENRE_ID, false);
+ assertHasGenre(program, COMEDY_GENRE_ID, false);
+ assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true);
+ }
+
+ @Test
+ public void testParcelable() {
+ List<CriticScore> criticScores = new ArrayList<>();
+ criticScores.add(new CriticScore("1", "2", "3"));
+ criticScores.add(new CriticScore("4", "5", "6"));
+ ImmutableList<TvContentRating> ratings =
+ ImmutableList.of(
+ TvContentRating.unflattenFromString("1/2/3"),
+ TvContentRating.unflattenFromString("4/5/6"));
+ ProgramImpl p =
+ new ProgramImpl.Builder()
+ .setId(1)
+ .setPackageName("2")
+ .setChannelId(3)
+ .setTitle("4")
+ .setSeriesId("5")
+ .setEpisodeTitle("6")
+ .setSeasonNumber("7")
+ .setSeasonTitle("8")
+ .setEpisodeNumber("9")
+ .setStartTimeUtcMillis(10)
+ .setEndTimeUtcMillis(11)
+ .setDescription("12")
+ .setLongDescription("12-long")
+ .setVideoWidth(13)
+ .setVideoHeight(14)
+ .setCriticScores(criticScores)
+ .setPosterArtUri("15")
+ .setThumbnailUri("16")
+ .setCanonicalGenres(Genres.encode(Genres.SPORTS, Genres.SHOPPING))
+ .setContentRatings(ratings)
+ .setRecordingProhibited(true)
+ .build();
+ Parcel p1 = Parcel.obtain();
+ Parcel p2 = Parcel.obtain();
+ try {
+ p.writeToParcel(p1, 0);
+ byte[] bytes = p1.marshall();
+ p2.unmarshall(bytes, 0, bytes.length);
+ p2.setDataPosition(0);
+ ProgramImpl r2 = ProgramImpl.fromParcel(p2);
+ assertThat(r2).isEqualTo(p);
+ } finally {
+ p1.recycle();
+ p2.recycle();
+ }
+ }
+
+ @Test
+ public void testParcelableWithCriticScore() {
+ ProgramImpl program =
+ new ProgramImpl.Builder()
+ .setTitle("MyTitle")
+ .addCriticScore(
+ new CriticScore(
+ "default source", "5/10", "http://testurl/testimage.jpg"))
+ .build();
+ Parcel parcel = Parcel.obtain();
+ program.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ Program programFromParcel = ProgramImpl.CREATOR.createFromParcel(parcel);
+
+ assertThat(programFromParcel.getCriticScores()).isNotNull();
+ assertThat(programFromParcel.getCriticScores().get(0).source).isEqualTo("default source");
+ assertThat(programFromParcel.getCriticScores().get(0).score).isEqualTo("5/10");
+ assertThat(programFromParcel.getCriticScores().get(0).logoUrl)
+ .isEqualTo("http://testurl/testimage.jpg");
+ }
+
+ @Test
+ public void getEpisodeContentDescription_blank() {
+ Program program = new ProgramImpl.Builder().build();
+ assertThat(program.getEpisodeContentDescription(RuntimeEnvironment.application)).isNull();
+ }
+
+ @Test
+ public void getEpisodeContentDescription_seasonEpisodeAndTitle() {
+ Program program =
+ new ProgramImpl.Builder()
+ .setSeasonNumber("1")
+ .setEpisodeNumber("2")
+ .setEpisodeTitle("The second one")
+ .build();
+ assertThat(program.getEpisodeContentDescription(RuntimeEnvironment.application))
+ .isEqualTo("Season 1 Episode 2 The second one");
+ }
+
+ @Test
+ public void getEpisodeContentDescription_EpisodeAndTitle() {
+ Program program =
+ new ProgramImpl.Builder()
+ .setEpisodeNumber("2")
+ .setEpisodeTitle("The second one")
+ .build();
+ assertThat(program.getEpisodeContentDescription(RuntimeEnvironment.application))
+ .isEqualTo("Episode 2 The second one");
+ }
+
+ @Test
+ public void getEpisodeContentDescription_seasonEpisode() {
+ Program program =
+ new ProgramImpl.Builder().setSeasonNumber("1").setEpisodeNumber("2").build();
+ assertThat(program.getEpisodeContentDescription(RuntimeEnvironment.application))
+ .isEqualTo("Season 1 Episode 2 ");
+ }
+
+ @Test
+ public void getEpisodeContentDescription_EpisodeTitle() {
+ Program program = new ProgramImpl.Builder().setEpisodeTitle("The second one").build();
+ assertThat(program.getEpisodeContentDescription(RuntimeEnvironment.application))
+ .isEqualTo("The second one");
+ }
+
+ @Test
+ public void getEpisodeDisplayTitle_blank() {
+ Program program = new ProgramImpl.Builder().build();
+ assertThat(program.getEpisodeDisplayTitle(RuntimeEnvironment.application)).isNull();
+ }
+
+ @Test
+ public void getEpisodeDisplayTitle_seasonEpisodeAndTitle() {
+ Program program =
+ new ProgramImpl.Builder()
+ .setSeasonNumber("1")
+ .setEpisodeNumber("2")
+ .setEpisodeTitle("The second one")
+ .build();
+ assertThat(program.getEpisodeDisplayTitle(RuntimeEnvironment.application))
+ .isEqualTo("S1: Ep. 2 The second one");
+ }
+
+ @Test
+ public void getEpisodeDisplayTitle_EpisodeTitle() {
+ Program program =
+ new ProgramImpl.Builder()
+ .setEpisodeNumber("2")
+ .setEpisodeTitle("The second one")
+ .build();
+ assertThat(program.getEpisodeDisplayTitle(RuntimeEnvironment.application))
+ .isEqualTo("Ep. 2 The second one");
+ }
+
+ @Test
+ public void getEpisodeDisplayTitle_seasonEpisode() {
+ Program program =
+ new ProgramImpl.Builder().setSeasonNumber("1").setEpisodeNumber("2").build();
+ assertThat(program.getEpisodeDisplayTitle(RuntimeEnvironment.application))
+ .isEqualTo("S1: Ep. 2 ");
+ }
+
+ @Test
+ public void getEpisodeDisplayTitle_episode() {
+ Program program = new ProgramImpl.Builder().setEpisodeTitle("The second one").build();
+ assertThat(program.getEpisodeDisplayTitle(RuntimeEnvironment.application))
+ .isEqualTo("The second one");
+ }
+
+ private static void assertNullCanonicalGenres(Program program) {
+ String[] actual = program.getCanonicalGenres();
+ assertWithMessage("Expected null canonical genres but was " + Arrays.toString(actual))
+ .that(actual)
+ .isNull();
+ }
+
+ private static void assertCanonicalGenres(Program program, String... expected) {
+ assertWithMessage("canonical genres")
+ .that(Arrays.asList(program.getCanonicalGenres()))
+ .isEqualTo(Arrays.asList(expected));
+ }
+
+ private static void assertHasGenre(Program program, int genreId, boolean expected) {
+ assertWithMessage("hasGenre(" + genreId + ")")
+ .that(program.hasGenre(genreId))
+ .isEqualTo(expected);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/TvInputNewComparatorTest.java b/tests/robotests/src/com/android/tv/data/TvInputNewComparatorTest.java
new file mode 100644
index 0000000..bffddf0
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/TvInputNewComparatorTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.content.pm.ResolveInfo;
+import android.media.tv.TvInputInfo;
+import android.util.Pair;
+
+import com.android.tv.testing.ComparatorTester;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.utils.TestUtils;
+import com.android.tv.util.SetupUtils;
+import com.android.tv.util.TvInputManagerHelper;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+
+/** Test for {@link TvInputNewComparator} */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class TvInputNewComparatorTest {
+ @Test
+ public void testComparator() throws Exception {
+ LinkedHashMap<String, Pair<Boolean, Boolean>> inputIdToNewInput = new LinkedHashMap<>();
+ inputIdToNewInput.put("2_new_input", Pair.create(true, false));
+ inputIdToNewInput.put("4_new_input", Pair.create(true, false));
+ inputIdToNewInput.put("4_old_input", Pair.create(false, false));
+ inputIdToNewInput.put("0_old_input", Pair.create(false, true));
+ inputIdToNewInput.put("1_old_input", Pair.create(false, true));
+ inputIdToNewInput.put("3_old_input", Pair.create(false, true));
+
+ SetupUtils setupUtils = Mockito.mock(SetupUtils.class);
+ Mockito.when(setupUtils.isNewInput(ArgumentMatchers.anyString()))
+ .thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ String inputId = (String) invocation.getArguments()[0];
+ return inputIdToNewInput.get(inputId).first;
+ }
+ });
+ Mockito.when(setupUtils.isSetupDone(ArgumentMatchers.anyString()))
+ .thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ String inputId = (String) invocation.getArguments()[0];
+ return inputIdToNewInput.get(inputId).second;
+ }
+ });
+ TvInputManagerHelper inputManager = Mockito.mock(TvInputManagerHelper.class);
+ Mockito.when(inputManager.getDefaultTvInputInfoComparator())
+ .thenReturn(
+ new Comparator<TvInputInfo>() {
+ @Override
+ public int compare(TvInputInfo lhs, TvInputInfo rhs) {
+ return lhs.getId().compareTo(rhs.getId());
+ }
+ });
+ TvInputNewComparator comparator = new TvInputNewComparator(setupUtils, inputManager);
+ ComparatorTester<TvInputInfo> comparatorTester =
+ ComparatorTester.withoutEqualsTest(comparator);
+ ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test");
+ for (String id : inputIdToNewInput.keySet()) {
+ // Put mock resolveInfo to prevent NPE in {@link TvInputInfo#toString}
+ TvInputInfo info1 =
+ TestUtils.createTvInputInfo(
+ resolveInfo, id, "test1", TvInputInfo.TYPE_TUNER, false);
+ TvInputInfo info2 =
+ TestUtils.createTvInputInfo(
+ resolveInfo, id, "test2", TvInputInfo.TYPE_DISPLAY_PORT, true);
+ TvInputInfo info3 =
+ TestUtils.createTvInputInfo(
+ resolveInfo, id, "test", TvInputInfo.TYPE_HDMI, true);
+ comparatorTester.addComparableGroup(info1, info2, info3);
+ }
+ comparatorTester.test();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/WatchedHistoryManagerTest.java b/tests/robotests/src/com/android/tv/data/WatchedHistoryManagerTest.java
new file mode 100644
index 0000000..9eca7d3
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/WatchedHistoryManagerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import com.android.tv.data.WatchedHistoryManager.WatchedRecord;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link WatchedHistoryManagerTest}. */
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class WatchedHistoryManagerTest {
+ // Wait time for expected success.
+ private static final int MAX_HISTORY_SIZE = 100;
+
+ private WatchedHistoryManager mWatchedHistoryManager;
+ private TestWatchedHistoryManagerListener mListener;
+
+ @Before
+ public void setUp() {
+ mWatchedHistoryManager =
+ new WatchedHistoryManager(
+ RuntimeEnvironment.application,
+ MAX_HISTORY_SIZE,
+ MoreExecutors.directExecutor());
+ mListener = new TestWatchedHistoryManagerListener();
+ mWatchedHistoryManager.setListener(mListener);
+ }
+
+ private void startAndWaitForComplete() {
+ mWatchedHistoryManager.start();
+ assertThat(mListener.mLoadFinished).isTrue();
+ }
+
+ @Test
+ public void testIsLoaded() {
+ startAndWaitForComplete();
+ assertThat(mWatchedHistoryManager.isLoaded()).isTrue();
+ }
+
+ @Test
+ public void testLogChannelViewStop() {
+ startAndWaitForComplete();
+ long fakeId = 100000000;
+ long time = System.currentTimeMillis();
+ long duration = TimeUnit.MINUTES.toMillis(10);
+ ChannelImpl channel = new ChannelImpl.Builder().setId(fakeId).build();
+ mWatchedHistoryManager.logChannelViewStop(channel, time, duration);
+
+ WatchedRecord record = mWatchedHistoryManager.getRecord(0);
+ WatchedRecord recordFromSharedPreferences =
+ mWatchedHistoryManager.getRecordFromSharedPreferences(0);
+ assertThat(fakeId).isEqualTo(record.channelId);
+ assertThat(time - duration).isEqualTo(record.watchedStartTime);
+ assertThat(duration).isEqualTo(record.duration);
+ assertThat(recordFromSharedPreferences).isEqualTo(record);
+ }
+
+ @Test
+ public void testCircularHistoryQueue() {
+ startAndWaitForComplete();
+ final long startChannelId = 100000000;
+ long time = System.currentTimeMillis();
+ long duration = TimeUnit.MINUTES.toMillis(10);
+
+ int size = MAX_HISTORY_SIZE * 2;
+ for (int i = 0; i < size; ++i) {
+ ChannelImpl channel = new ChannelImpl.Builder().setId(startChannelId + i).build();
+ mWatchedHistoryManager.logChannelViewStop(channel, time + duration * i, duration);
+ }
+ for (int i = 0; i < MAX_HISTORY_SIZE; ++i) {
+ WatchedRecord record = mWatchedHistoryManager.getRecord(i);
+ WatchedRecord recordFromSharedPreferences =
+ mWatchedHistoryManager.getRecordFromSharedPreferences(i);
+ assertThat(recordFromSharedPreferences).isEqualTo(record);
+ assertThat(startChannelId + size - 1 - i).isEqualTo(record.channelId);
+ }
+ // Since the WatchedHistory is a circular queue, the value for 0 and maxHistorySize
+ // are same.
+ assertThat(mWatchedHistoryManager.getRecordFromSharedPreferences(MAX_HISTORY_SIZE))
+ .isEqualTo(mWatchedHistoryManager.getRecordFromSharedPreferences(0));
+ }
+
+ @Test
+ public void testWatchedRecordEquals() {
+ assertThat(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 3))).isTrue();
+ assertThat(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 4))).isFalse();
+ assertThat(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 4, 3))).isFalse();
+ assertThat(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(4, 2, 3))).isFalse();
+ }
+
+ @Test
+ public void testEncodeDecodeWatchedRecord() {
+ long fakeId = 100000000;
+ long time = System.currentTimeMillis();
+ long duration = TimeUnit.MINUTES.toMillis(10);
+ WatchedRecord record = new WatchedRecord(fakeId, time, duration);
+ WatchedRecord sameRecord =
+ mWatchedHistoryManager.decode(mWatchedHistoryManager.encode(record));
+ assertThat(sameRecord).isEqualTo(record);
+ }
+
+ private static final class TestWatchedHistoryManagerListener
+ implements WatchedHistoryManager.Listener {
+ boolean mLoadFinished;
+
+ @Override
+ public void onLoadFinished() {
+ mLoadFinished = true;
+ }
+
+ @Override
+ public void onNewRecordAdded(WatchedRecord watchedRecord) {}
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/api/ProgramTest.java b/tests/robotests/src/com/android/tv/data/api/ProgramTest.java
new file mode 100644
index 0000000..3b9f062
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/api/ProgramTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 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.data.api;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.testing.constants.ConfigConstants;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link Program}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class ProgramTest {
+
+ private static ProgramImpl createProgramWithStartEndTimes(
+ long startTimeUtcMillis, long endTimeUtcMillis) {
+ return new ProgramImpl.Builder()
+ .setStartTimeUtcMillis(startTimeUtcMillis)
+ .setEndTimeUtcMillis(endTimeUtcMillis)
+ .build();
+ }
+
+ private static ProgramImpl createProgramWithChannelId(long channelId) {
+ return new ProgramImpl.Builder().setChannelId(channelId).build();
+ }
+
+ private final Program start10end20 = createProgramWithStartEndTimes(10, 20);
+ private final Program channel1 = createProgramWithChannelId(1);
+
+ @Test
+ public void sameChannel_nullAlwaysFalse() {
+ assertThat(Program.sameChannel(null, null)).isFalse();
+ assertThat(Program.sameChannel(channel1, null)).isFalse();
+ assertThat(Program.sameChannel(null, channel1)).isFalse();
+ }
+
+ @Test
+ public void sameChannel_true() {
+ assertThat(Program.sameChannel(channel1, channel1)).isTrue();
+ assertThat(Program.sameChannel(channel1, createProgramWithChannelId(1))).isTrue();
+ }
+
+ @Test
+ public void sameChannel_false() {
+ assertThat(Program.sameChannel(channel1, createProgramWithChannelId(2))).isFalse();
+ }
+
+ @Test
+ public void isOverLapping_nullAlwaysFalse() {
+ assertThat(Program.isOverlapping(null, null)).isFalse();
+ assertThat(Program.isOverlapping(start10end20, null)).isFalse();
+ assertThat(Program.isOverlapping(null, start10end20)).isFalse();
+ }
+
+ @Test
+ public void isOverLapping_same() {
+ assertThat(Program.isOverlapping(start10end20, start10end20)).isTrue();
+ }
+
+ @Test
+ public void isOverLapping_endBefore() {
+ assertThat(Program.isOverlapping(start10end20, createProgramWithStartEndTimes(1, 9)))
+ .isFalse();
+ }
+
+ @Test
+ public void isOverLapping_endAtStart() {
+ assertThat(Program.isOverlapping(start10end20, createProgramWithStartEndTimes(1, 10)))
+ .isFalse();
+ }
+
+ @Test
+ public void isOverLapping_endDuring() {
+ assertThat(Program.isOverlapping(start10end20, createProgramWithStartEndTimes(1, 11)))
+ .isTrue();
+ }
+
+ @Test
+ public void isOverLapping_startAfter() {
+ assertThat(Program.isOverlapping(start10end20, createProgramWithStartEndTimes(21, 30)))
+ .isFalse();
+ }
+
+ @Test
+ public void isOverLapping_beginAtEnd() {
+ assertThat(Program.isOverlapping(start10end20, createProgramWithStartEndTimes(20, 30)))
+ .isFalse();
+ }
+
+ @Test
+ public void isOverLapping_beginBeforeEnd() {
+ assertThat(Program.isOverlapping(start10end20, createProgramWithStartEndTimes(19, 30)))
+ .isTrue();
+ }
+
+ @Test
+ public void isOverLapping_inside() {
+ assertThat(Program.isOverlapping(start10end20, createProgramWithStartEndTimes(11, 19)))
+ .isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/epg/EpgFetcherImplTest.java b/tests/robotests/src/com/android/tv/data/epg/EpgFetcherImplTest.java
new file mode 100644
index 0000000..1df89b9
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/epg/EpgFetcherImplTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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.data.epg;
+
+/** Tests for {@link EpgFetcher}. */
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.sqlite.SQLiteDatabase;
+import android.media.tv.TvContract;
+import androidx.tvprovider.media.tv.Channel;
+import com.android.tv.common.CommonPreferences;
+import com.android.tv.common.buildtype.HasBuildType.BuildType;
+import com.android.tv.common.flags.impl.DefaultBackendKnobsFlags;
+import com.android.tv.common.flags.impl.SettableFlagsModule;
+import com.android.tv.common.util.PostalCodeUtils;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.features.TvFeatures;
+import com.android.tv.perf.PerformanceMonitor;
+import com.android.tv.perf.stub.StubPerformanceMonitor;
+import com.android.tv.testing.DbTestingUtils;
+import com.android.tv.testing.EpgTestData;
+import com.android.tv.testing.FakeEpgReader;
+import com.android.tv.testing.FakeTvInputManagerHelper;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.fakes.FakeClock;
+import com.android.tv.testing.fakes.FakeTvProvider;
+import com.android.tv.testing.robo.ContentProviders;
+import com.google.android.tv.livechannels.epg.provider.EpgContentProvider;
+import com.google.android.tv.partner.support.EpgContract;
+import com.google.common.collect.ImmutableList;
+import dagger.Component;
+import dagger.Module;
+import dagger.android.AndroidInjectionModule;
+import dagger.android.AndroidInjector;
+import dagger.android.DispatchingAndroidInjector;
+import dagger.android.HasAndroidInjector;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import javax.inject.Inject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.android.util.concurrent.RoboExecutorService;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link EpgFetcherImpl}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = EpgFetcherImplTest.TestApp.class)
+public class EpgFetcherImplTest {
+
+ /** TestApp for {@link EpgFetcherImplTest} */
+ public static class TestApp extends TestSingletonApp implements HasAndroidInjector {
+ @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ DaggerEpgFetcherImplTest_TestAppComponent.builder()
+ .settableFlagsModule(flagsModule)
+ .build()
+ .inject(this);
+ }
+
+ @Override
+ public AndroidInjector<Object> androidInjector() {
+ return dispatchingAndroidInjector;
+ }
+ }
+
+ /** Component for {@link EpgFetcherImplTest} */
+ @Component(
+ modules = {
+ AndroidInjectionModule.class,
+ TestModule.class,
+ EpgContentProvider.Module.class
+ })
+ interface TestAppComponent extends AndroidInjector<TestApp> {}
+
+ /** Module for {@link EpgFetcherImplTest} */
+ @Module(includes = {SettableFlagsModule.class})
+ public static class TestModule {}
+
+ private static final String[] PROGRAM_COLUMNS = {
+ TvContract.Programs.COLUMN_CHANNEL_ID,
+ TvContract.Programs.COLUMN_TITLE,
+ TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
+ TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS
+ };
+
+ private static final String[] CHANNEL_COLUMNS = {
+ TvContract.Channels.COLUMN_DISPLAY_NAME,
+ TvContract.Channels.COLUMN_DISPLAY_NUMBER,
+ TvContract.Channels.COLUMN_NETWORK_AFFILIATION
+ };
+
+ private FakeClock mFakeClock;
+ private EpgFetcherImpl mEpgFetcher;
+ private ChannelDataManager mChannelDataManager;
+ private FakeEpgReader mEpgReader;
+ private PerformanceMonitor mPerformanceMonitor = new StubPerformanceMonitor();
+ private ContentResolver mContentResolver;
+ private FakeTvProvider mTvProvider;
+ private EpgContentProvider mEpgProvider;
+ private EpgContentProvider.EpgDatabaseHelper mDatabaseHelper;
+ private TestApp mTestApp;
+
+ @Before
+ public void setup() {
+
+ TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest();
+ mTestApp = (TestApp) RuntimeEnvironment.application;
+ Shadows.shadowOf(RuntimeEnvironment.application)
+ .grantPermissions("com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA");
+ mDatabaseHelper = new EpgContentProvider.EpgDatabaseHelper(RuntimeEnvironment.application);
+ CommonPreferences.initialize(RuntimeEnvironment.application);
+ PostalCodeUtils.setLastPostalCode(RuntimeEnvironment.application, "90210");
+ EpgFetchHelper.setLastLineupId(RuntimeEnvironment.application, "test90210");
+ mTvProvider = ContentProviders.register(FakeTvProvider.class, TvContract.AUTHORITY);
+ mEpgProvider = ContentProviders.register(EpgContentProvider.class, EpgContract.AUTHORITY);
+ mEpgProvider.setCallingPackage_("com.google.android.tv");
+ mFakeClock = FakeClock.createWithCurrentTime();
+ FakeTvInputManagerHelper fakeTvInputManagerHelper =
+ new FakeTvInputManagerHelper(RuntimeEnvironment.application);
+ mContentResolver = RuntimeEnvironment.application.getContentResolver();
+ mChannelDataManager =
+ new ChannelDataManager(
+ RuntimeEnvironment.application,
+ fakeTvInputManagerHelper,
+ new RoboExecutorService(),
+ mContentResolver);
+ fakeTvInputManagerHelper.start();
+ mChannelDataManager.start();
+ mEpgReader = new FakeEpgReader(mFakeClock);
+ mEpgFetcher =
+ new EpgFetcherImpl(
+ RuntimeEnvironment.application,
+ new EpgInputWhiteList(
+ mTestApp.flagsModule.cloudEpgFlags,
+ mTestApp.flagsModule.legacyFlags),
+ mChannelDataManager,
+ mEpgReader,
+ mPerformanceMonitor,
+ mFakeClock,
+ new DefaultBackendKnobsFlags(),
+ BuildType.NO_JNI_TEST);
+ EpgTestData.DATA_90210.loadData(mFakeClock, mEpgReader); // This also sets fake clock
+ EpgFetchHelper.setLastEpgUpdatedTimestamp(
+ RuntimeEnvironment.application,
+ mFakeClock.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
+ }
+
+ @After
+ public void after() {
+ mChannelDataManager.stop();
+ TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.resetForTests();
+ }
+
+ @Test
+ public void fetchImmediately_nochannels() throws ExecutionException, InterruptedException {
+ EpgFetcherImpl.FetchAsyncTask fetcherTask = mEpgFetcher.createFetchTask(null, null);
+ fetcherTask.execute();
+
+ assertThat(fetcherTask.get()).isEqualTo(EpgFetcherImpl.REASON_NO_BUILT_IN_CHANNELS);
+ List<List<String>> rows =
+ DbTestingUtils.toList(
+ mContentResolver.query(
+ TvContract.Programs.CONTENT_URI,
+ PROGRAM_COLUMNS,
+ null,
+ null,
+ null));
+ assertThat(rows).isEmpty();
+ }
+
+ @Test
+ public void fetchImmediately_testChannel() throws ExecutionException, InterruptedException {
+ // The channels must be in the app package.
+ // For this test the package is com.android.tv.data.epg
+ insertTestChannels(
+ "com.android.tv.data.epg/.tuner.TunerTvInputService", EpgTestData.CHANNEL_10);
+ EpgFetcherImpl.FetchAsyncTask fetcherTask = mEpgFetcher.createFetchTask(null, null);
+ fetcherTask.execute();
+
+ assertThat(fetcherTask.get()).isNull();
+ List<List<String>> rows =
+ DbTestingUtils.toList(
+ mContentResolver.query(
+ TvContract.Programs.CONTENT_URI,
+ PROGRAM_COLUMNS,
+ null,
+ null,
+ null));
+ assertThat(rows)
+ .containsExactly(
+ ImmutableList.of("1", "Program 1", "1496358000000", "1496359800000"));
+ }
+
+ @Test
+ public void fetchImmediately_epgChannel() throws ExecutionException, InterruptedException {
+ mTestApp.flagsModule.cloudEpgFlags.setThirdPartyEpgInputCsv("com.example/.Input");
+ insertTestChannels("com.example/.Input", EpgTestData.CHANNEL_10, EpgTestData.CHANNEL_11);
+ createTestEpgInput();
+ EpgFetcherImpl.FetchAsyncTask fetcherTask = mEpgFetcher.createFetchTask(null, null);
+ fetcherTask.execute();
+
+ assertThat(fetcherTask.get()).isNull();
+ List<List<String>> rows =
+ DbTestingUtils.toList(
+ mContentResolver.query(
+ TvContract.Programs.CONTENT_URI,
+ PROGRAM_COLUMNS,
+ null,
+ null,
+ null));
+ assertThat(rows)
+ .containsExactly(
+ ImmutableList.of("1", "Program 1", "1496358000000", "1496359800000"),
+ ImmutableList.of("2", "Program 2", "1496359800000", "1496361600000"));
+ }
+
+ @Test
+ public void testUpdateNetworkAffiliation() throws ExecutionException, InterruptedException {
+ if (!TvFeatures.STORE_NETWORK_AFFILIATION.isEnabled(RuntimeEnvironment.application)) {
+ return;
+ }
+ // set network affiliation to null so that it can be updated later
+ Channel channel =
+ new Channel.Builder(EpgTestData.CHANNEL_10).setNetworkAffiliation(null).build();
+ // The channels must be in the app package.
+ // For this test the package is com.android.tv.data.epg
+ insertTestChannels("com.android.tv.data.epg/.tuner.TunerTvInputService", channel);
+
+ List<List<String>> rows =
+ DbTestingUtils.toList(
+ mContentResolver.query(
+ TvContract.Channels.CONTENT_URI,
+ CHANNEL_COLUMNS,
+ null,
+ null,
+ null));
+ assertThat(rows).containsExactly(ImmutableList.of("Channel TEN", "10", "null"));
+ EpgFetcherImpl.FetchAsyncTask fetcherTask = mEpgFetcher.createFetchTask(null, null);
+ fetcherTask.execute();
+
+ assertThat(fetcherTask.get()).isNull();
+ rows =
+ DbTestingUtils.toList(
+ mContentResolver.query(
+ TvContract.Channels.CONTENT_URI,
+ CHANNEL_COLUMNS,
+ null,
+ null,
+ null));
+ // network affiliation should be updated
+ assertThat(rows)
+ .containsExactly(
+ ImmutableList.of("Channel TEN", "10", "Channel 10 Network Affiliation"));
+ }
+
+ protected void insertTestChannels(String inputId, Channel... channels) {
+
+ for (Channel channel : channels) {
+ ContentValues values =
+ new Channel.Builder(channel).setInputId(inputId).build().toContentValues();
+ String packageName = inputId.substring(0, inputId.indexOf('/'));
+ mTvProvider.setCallingPackage(packageName);
+ mContentResolver.insert(TvContract.Channels.CONTENT_URI, values);
+ mTvProvider.setCallingPackage("com.android.tv");
+ }
+ }
+
+ private void createTestEpgInput() {
+ // Use the database helper so we can set the package name.
+ SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(EpgContract.EpgInputs.COLUMN_ID, "1");
+ values.put(EpgContract.EpgInputs.COLUMN_PACKAGE_NAME, "com.example");
+ values.put(EpgContract.EpgInputs.COLUMN_INPUT_ID, "com.example/.Input");
+ values.put(EpgContract.EpgInputs.COLUMN_LINEUP_ID, "lineup1");
+ long rowId = db.insert("epg_input", null, values);
+ assertThat(rowId).isEqualTo(1);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/data/epg/EpgInputWhiteListTest.java b/tests/robotests/src/com/android/tv/data/epg/EpgInputWhiteListTest.java
new file mode 100644
index 0000000..c018bd2
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/data/epg/EpgInputWhiteListTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.data.epg;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.common.flags.impl.DefaultCloudEpgFlags;
+import com.android.tv.common.flags.impl.DefaultLegacyFlags;
+import com.android.tv.features.TvFeatures;
+import com.android.tv.testing.constants.ConfigConstants;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link EpgInputWhiteList}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class EpgInputWhiteListTest {
+
+ private EpgInputWhiteList mWhiteList;
+ private DefaultCloudEpgFlags mCloudEpgFlags;
+ private DefaultLegacyFlags mLegacyFlags;
+
+ @Before
+ public void setup() {
+ TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest();
+ mCloudEpgFlags = new DefaultCloudEpgFlags();
+ mLegacyFlags = DefaultLegacyFlags.DEFAULT;
+ mWhiteList = new EpgInputWhiteList(mCloudEpgFlags, mLegacyFlags);
+ }
+
+ @After
+ public void after() {
+ TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.resetForTests();
+ }
+
+ @Test
+ public void isInputWhiteListed_noRemoteConfig() {
+ assertThat(mWhiteList.isInputWhiteListed("com.example/.Foo")).isFalse();
+ }
+
+ @Test
+ public void isInputWhiteListed_noMatch() {
+ mCloudEpgFlags.setThirdPartyEpgInputCsv("com.example/.Bar");
+ assertThat(mWhiteList.isInputWhiteListed("com.example/.Foo")).isFalse();
+ }
+
+ @Test
+ public void isInputWhiteListed_match() {
+ mCloudEpgFlags.setThirdPartyEpgInputCsv("com.example/.Foo");
+ assertThat(mWhiteList.isInputWhiteListed("com.example/.Foo")).isTrue();
+ }
+
+ @Test
+ public void isInputWhiteListed_matchWithTwo() {
+ mCloudEpgFlags.setThirdPartyEpgInputCsv("com.example/.Foo,com.example/.Bar");
+ assertThat(mWhiteList.isInputWhiteListed("com.example/.Foo")).isTrue();
+ }
+
+ @Test
+ public void toInputSet_withNewLine() {
+ assertThat(EpgInputWhiteList.toInputSet("com.example/.Foo,\ncom.example/.Bar\n"))
+ .containsExactly("com.example/.Foo", "com.example/.Bar");
+ }
+
+ @Test
+ public void toInputSet_withWhiteSpace() {
+ assertThat(EpgInputWhiteList.toInputSet("com.example/.Foo , com.example/.Bar "))
+ .containsExactly("com.example/.Foo", "com.example/.Bar");
+ }
+
+ @Test
+ public void isPackageWhiteListed_noRemoteConfig() {
+ assertThat(mWhiteList.isPackageWhiteListed("com.example")).isFalse();
+ }
+
+ @Test
+ public void isPackageWhiteListed_noMatch() {
+ mCloudEpgFlags.setThirdPartyEpgInputCsv("com.example/.Bar");
+ assertThat(mWhiteList.isPackageWhiteListed("com.other")).isFalse();
+ }
+
+ @Test
+ public void isPackageWhiteListed_match() {
+ mCloudEpgFlags.setThirdPartyEpgInputCsv("com.example/.Foo");
+ assertThat(mWhiteList.isPackageWhiteListed("com.example")).isTrue();
+ }
+
+ @Test
+ public void isPackageWhiteListed_matchWithTwo() {
+ mCloudEpgFlags.setThirdPartyEpgInputCsv("com.example/.Foo,com.example/.Bar");
+ assertThat(mWhiteList.isPackageWhiteListed("com.example")).isTrue();
+ }
+
+ @Test
+ public void isPackageWhiteListed_matchBadInput() {
+ mCloudEpgFlags.setThirdPartyEpgInputCsv("com.example.Foo");
+ assertThat(mWhiteList.isPackageWhiteListed("com.example")).isFalse();
+ }
+
+ @Test
+ public void isPackageWhiteListed_tunerInput() {
+ EpgInputWhiteList whiteList =
+ new EpgInputWhiteList(new DefaultCloudEpgFlags(), DefaultLegacyFlags.DEFAULT);
+ assertThat(
+ whiteList.isInputWhiteListed(
+ "com.google.android.tv/.tuner.tvinput.TunerTvInputService"))
+ .isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/BaseDvrDataManagerTest.java b/tests/robotests/src/com/android/tv/dvr/BaseDvrDataManagerTest.java
new file mode 100644
index 0000000..636bf2d
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/BaseDvrDataManagerTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 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.dvr;
+
+import android.os.Build;
+import android.support.annotation.NonNull;
+import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.feature.TestableFeature;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.DvrDataManagerInMemoryImpl;
+import com.android.tv.testing.dvr.RecordingTestUtils;
+import com.android.tv.testing.fakes.FakeClock;
+import com.google.common.truth.Truth;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link BaseDvrDataManager} using {@link DvrDataManagerInMemoryImpl}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class BaseDvrDataManagerTest {
+ private static final String INPUT_ID = "input_id";
+ private static final int CHANNEL_ID = 273;
+
+ private final TestableFeature mDvrFeature = CommonFeatures.DVR;
+ private DvrDataManagerInMemoryImpl mDvrDataManager;
+ private FakeClock mFakeClock;
+
+ @Before
+ public void setUp() {
+ mDvrFeature.enableForTest();
+ mFakeClock = FakeClock.createWithCurrentTime();
+ mDvrDataManager =
+ new DvrDataManagerInMemoryImpl(RuntimeEnvironment.application, mFakeClock);
+ }
+
+ @After
+ public void tearDown() {
+ mDvrFeature.resetForTests();
+ }
+
+ @Test
+ public void testGetNonStartedScheduledRecordings() {
+ ScheduledRecording recording =
+ mDvrDataManager.addScheduledRecordingInternal(
+ createNewScheduledRecordingStartingNow());
+ List<ScheduledRecording> result = mDvrDataManager.getNonStartedScheduledRecordings();
+ Truth.assertThat(result).containsExactly(recording);
+ }
+
+ @Test
+ public void testGetNonStartedScheduledRecordings_past() {
+ mDvrDataManager.addScheduledRecordingInternal(createNewScheduledRecordingStartingNow());
+ mFakeClock.increment(TimeUnit.MINUTES, 6);
+ List<ScheduledRecording> result = mDvrDataManager.getNonStartedScheduledRecordings();
+ Truth.assertThat(result).isEmpty();
+ }
+
+ @NonNull
+ private ScheduledRecording createNewScheduledRecordingStartingNow() {
+ return ScheduledRecording.buildFrom(
+ RecordingTestUtils.createTestRecordingWithIdAndPeriod(
+ ScheduledRecording.ID_NOT_SET,
+ INPUT_ID,
+ CHANNEL_ID,
+ mFakeClock.currentTimeMillis(),
+ mFakeClock.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5)))
+ .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)
+ .build();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/DvrDataManagerImplTest.java b/tests/robotests/src/com/android/tv/dvr/DvrDataManagerImplTest.java
new file mode 100644
index 0000000..8291c3a
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/DvrDataManagerImplTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 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.dvr;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Build;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.RecordingTestUtils;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link DvrDataManagerImpl} */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class DvrDataManagerImplTest {
+ private static final String INPUT_ID = "input_id";
+ private static final int CHANNEL_ID = 273;
+
+ @Test
+ public void testGetNextScheduledStartTimeAfter() {
+ long id = 1;
+ List<ScheduledRecording> scheduledRecordings = new ArrayList<>();
+ assertNextStartTime(scheduledRecordings, 0L, DvrDataManager.NEXT_START_TIME_NOT_FOUND);
+ scheduledRecordings.add(
+ RecordingTestUtils.createTestRecordingWithIdAndPeriod(
+ id++, INPUT_ID, CHANNEL_ID, 10L, 20L));
+ assertNextStartTime(scheduledRecordings, 9L, 10L);
+ assertNextStartTime(scheduledRecordings, 10L, DvrDataManager.NEXT_START_TIME_NOT_FOUND);
+ scheduledRecordings.add(
+ RecordingTestUtils.createTestRecordingWithIdAndPeriod(
+ id++, INPUT_ID, CHANNEL_ID, 20L, 30L));
+ assertNextStartTime(scheduledRecordings, 9L, 10L);
+ assertNextStartTime(scheduledRecordings, 10L, 20L);
+ assertNextStartTime(scheduledRecordings, 20L, DvrDataManager.NEXT_START_TIME_NOT_FOUND);
+ scheduledRecordings.add(
+ RecordingTestUtils.createTestRecordingWithIdAndPeriod(
+ id++, INPUT_ID, CHANNEL_ID, 30L, 40L));
+ assertNextStartTime(scheduledRecordings, 9L, 10L);
+ assertNextStartTime(scheduledRecordings, 10L, 20L);
+ assertNextStartTime(scheduledRecordings, 20L, 30L);
+ assertNextStartTime(scheduledRecordings, 30L, DvrDataManager.NEXT_START_TIME_NOT_FOUND);
+ scheduledRecordings.clear();
+ scheduledRecordings.add(
+ RecordingTestUtils.createTestRecordingWithIdAndPeriod(
+ id++, INPUT_ID, CHANNEL_ID, 10L, 20L));
+ scheduledRecordings.add(
+ RecordingTestUtils.createTestRecordingWithIdAndPeriod(
+ id++, INPUT_ID, CHANNEL_ID, 10L, 20L));
+ scheduledRecordings.add(
+ RecordingTestUtils.createTestRecordingWithIdAndPeriod(
+ id++, INPUT_ID, CHANNEL_ID, 10L, 20L));
+ assertNextStartTime(scheduledRecordings, 9L, 10L);
+ assertNextStartTime(scheduledRecordings, 10L, DvrDataManager.NEXT_START_TIME_NOT_FOUND);
+ }
+
+ private void assertNextStartTime(
+ List<ScheduledRecording> scheduledRecordings, long startTime, long expected) {
+ assertWithMessage("getNextScheduledStartTimeAfter()")
+ .that(DvrDataManagerImpl.getNextStartTimeAfter(scheduledRecordings, startTime))
+ .isEqualTo(expected);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/DvrScheduleManagerTest.java b/tests/robotests/src/com/android/tv/dvr/DvrScheduleManagerTest.java
new file mode 100644
index 0000000..d0a58c9
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/DvrScheduleManagerTest.java
@@ -0,0 +1,879 @@
+/*
+ * Copyright (C) 2016 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.dvr;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import android.os.Build;
+import android.util.Range;
+import com.android.tv.dvr.DvrScheduleManager.ConflictInfo;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.RecordingTestUtils;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link DvrScheduleManager} */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class DvrScheduleManagerTest {
+ private static final String INPUT_ID = "input_id";
+
+ @Test
+ public void testGetConflictingSchedules_emptySchedule() {
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1)).isEmpty();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_noConflict() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1)).isEmpty();
+
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2)).isEmpty();
+
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2)).isEmpty();
+
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3)).isEmpty();
+
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3)).isEmpty();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_noTuner() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 0)).isEmpty();
+
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 0)).isEqualTo(schedules);
+ schedules.add(
+ 0,
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 0)).isEqualTo(schedules);
+ }
+
+ @Test
+ public void testGetConflictingSchedules_conflict() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r1);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1)).isEmpty();
+
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(r2);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2)).isEmpty();
+
+ ScheduledRecording r3 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r3);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2)).isEmpty();
+
+ ScheduledRecording r4 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(r4);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3)).isEmpty();
+
+ ScheduledRecording r5 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r5);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3)).isEmpty();
+
+ ScheduledRecording r6 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 10L, 90L);
+ schedules.add(r6);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r4, r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 4)).isEmpty();
+
+ ScheduledRecording r7 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 110L, 190L);
+ schedules.add(r7);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r5, r4, r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 4)).isEmpty();
+
+ ScheduledRecording r8 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 50L, 150L);
+ schedules.add(r8);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r7, r6, r5, r4, r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r5, r4, r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3))
+ .containsExactly(r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 4))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 5)).isEmpty();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_conflict2() {
+ // The case when there is a long schedule.
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 1000L);
+ schedules.add(r1);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1)).isEmpty();
+
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(r2);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2)).isEmpty();
+
+ ScheduledRecording r3 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r3);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2)).isEmpty();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_reverseOrder() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(0, r1);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1)).isEmpty();
+
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(0, r2);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2)).isEmpty();
+
+ ScheduledRecording r3 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(0, r3);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2)).isEmpty();
+
+ ScheduledRecording r4 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(0, r4);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3)).isEmpty();
+
+ ScheduledRecording r5 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(0, r5);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3)).isEmpty();
+
+ ScheduledRecording r6 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 10L, 90L);
+ schedules.add(0, r6);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r4, r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 4)).isEmpty();
+
+ ScheduledRecording r7 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 110L, 190L);
+ schedules.add(0, r7);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r5, r4, r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 4)).isEmpty();
+
+ ScheduledRecording r8 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 50L, 150L);
+ schedules.add(0, r8);
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r7, r6, r5, r4, r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 2))
+ .containsExactly(r5, r4, r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3))
+ .containsExactly(r3, r2, r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 4))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 5)).isEmpty();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_period1() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(r2);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ schedules, 1, Collections.singletonList(new Range<>(10L, 20L))))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ schedules, 1, Collections.singletonList(new Range<>(110L, 120L))))
+ .containsExactly(r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_period2() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r2);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ schedules, 1, Collections.singletonList(new Range<>(10L, 20L))))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ schedules, 1, Collections.singletonList(new Range<>(110L, 120L))))
+ .containsExactly(r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_period3() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r2);
+ ScheduledRecording r3 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(r3);
+ ScheduledRecording r4 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r4);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ schedules, 1, Collections.singletonList(new Range<>(10L, 20L))))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ schedules, 1, Collections.singletonList(new Range<>(110L, 120L))))
+ .containsExactly(r2)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ schedules, 1, Collections.singletonList(new Range<>(50L, 150L))))
+ .containsExactly(r2, r1)
+ .inOrder();
+ List<Range<Long>> ranges = new ArrayList<>();
+ ranges.add(new Range<>(10L, 20L));
+ ranges.add(new Range<>(110L, 120L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1, ranges))
+ .containsExactly(r2, r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_addSchedules1() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 100L);
+ schedules.add(r2);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ Collections.singletonList(
+ ScheduledRecording.builder(INPUT_ID, ++channelId, 10L, 20L)
+ .setPriority(++priority)
+ .build()),
+ schedules,
+ 1))
+ .containsExactly(r2, r1)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ Collections.singletonList(
+ ScheduledRecording.builder(
+ INPUT_ID, ++channelId, 110L, 120L)
+ .setPriority(++priority)
+ .build()),
+ schedules,
+ 1))
+ .containsExactly(r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_addSchedules2() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r2);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ Collections.singletonList(
+ ScheduledRecording.builder(INPUT_ID, ++channelId, 10L, 20L)
+ .setPriority(++priority)
+ .build()),
+ schedules,
+ 1))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ Collections.singletonList(
+ ScheduledRecording.builder(
+ INPUT_ID, ++channelId, 110L, 120L)
+ .setPriority(++priority)
+ .build()),
+ schedules,
+ 1))
+ .containsExactly(r2, r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_addLowestPriority() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 400L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r2);
+ // Returning r1 even though r1 has the higher priority than the new one. That's because r1
+ // starts at 0 and stops at 100, and the new one will be recorded successfully.
+ assertThat(
+ DvrScheduleManager.getConflictingSchedules(
+ Collections.singletonList(
+ ScheduledRecording.builder(
+ INPUT_ID, ++channelId, 200L, 300L)
+ .setPriority(0)
+ .build()),
+ schedules,
+ 1))
+ .containsExactly(r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedules_sameChannel() {
+ long priority = 0;
+ long channelId = 1;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelId, ++priority, 0L, 200L));
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelId, ++priority, 0L, 200L));
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 3)).isEmpty();
+ }
+
+ @Test
+ public void testGetConflictingSchedule_startEarlyAndFail() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 200L, 300L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 400L);
+ schedules.add(r2);
+ ScheduledRecording r3 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 200L);
+ schedules.add(r3);
+ // r2 starts recording and fails when r3 starts. r1 is recorded successfully.
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r2)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedule_startLate() {
+ long priority = 0;
+ long channelId = 0;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 200L, 400L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 100L, 300L);
+ schedules.add(r2);
+ ScheduledRecording r3 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r3);
+ // r2 and r1 are clipped.
+ assertThat(DvrScheduleManager.getConflictingSchedules(schedules, 1))
+ .containsExactly(r2, r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedulesForTune_canTune() {
+ // Can tune to the recorded channel if tuner count is 1.
+ long priority = 0;
+ long channelId = 1;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelId, ++priority, 0L, 200L));
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForTune(
+ INPUT_ID, channelId, 0L, priority + 1, schedules, 1))
+ .isEmpty();
+ }
+
+ @Test
+ public void testGetConflictingSchedulesForTune_cannotTune() {
+ // Can't tune to a channel if other channel is recording and tuner count is 1.
+ long priority = 0;
+ long channelId = 1;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ schedules.add(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelId, ++priority, 0L, 200L));
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForTune(
+ INPUT_ID, channelId + 1, 0L, priority + 1, schedules, 1))
+ .containsExactly(schedules.get(0))
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedulesForWatching_otherChannels() {
+ // The other channels are to be recorded.
+ long priority = 0;
+ long channelToWatch = 1;
+ long channelId = 1;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r2);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3))
+ .isEmpty();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2))
+ .containsExactly(r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedulesForWatching_sameChannel1() {
+ long priority = 0;
+ long channelToWatch = 1;
+ long channelId = 1;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelToWatch, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r2);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2))
+ .isEmpty();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1))
+ .containsExactly(r2)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedulesForWatching_sameChannel2() {
+ long priority = 0;
+ long channelToWatch = 1;
+ long channelId = 1;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelToWatch, ++priority, 0L, 200L);
+ schedules.add(r2);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2))
+ .isEmpty();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1))
+ .containsExactly(r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedulesForWatching_sameChannelConflict1() {
+ long priority = 0;
+ long channelToWatch = 1;
+ long channelId = 1;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelToWatch, ++priority, 0L, 200L);
+ schedules.add(r2);
+ ScheduledRecording r3 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelToWatch, ++priority, 0L, 200L);
+ schedules.add(r3);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3))
+ .containsExactly(r2)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2))
+ .containsExactly(r2)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1))
+ .containsExactly(r2, r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testGetConflictingSchedulesForWatching_sameChannelConflict2() {
+ long priority = 0;
+ long channelToWatch = 1;
+ long channelId = 1;
+ List<ScheduledRecording> schedules = new ArrayList<>();
+ ScheduledRecording r1 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelToWatch, ++priority, 0L, 200L);
+ schedules.add(r1);
+ ScheduledRecording r2 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ channelToWatch, ++priority, 0L, 200L);
+ schedules.add(r2);
+ ScheduledRecording r3 =
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, ++priority, 0L, 200L);
+ schedules.add(r3);
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2))
+ .containsExactly(r1)
+ .inOrder();
+ assertThat(
+ DvrScheduleManager.getConflictingSchedulesForWatching(
+ INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1))
+ .containsExactly(r3, r1)
+ .inOrder();
+ }
+
+ @Test
+ public void testPartiallyConflictingSchedules() {
+ long priority = 100;
+ long channelId = 0;
+ List<ScheduledRecording> schedules =
+ new ArrayList<>(
+ Arrays.asList(
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 0L, 400L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 0L, 200L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 200L, 500L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 400L, 600L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 700L, 800L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 600L, 900L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 800L, 900L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 800L, 900L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 750L, 850L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 300L, 450L),
+ RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(
+ ++channelId, --priority, 50L, 900L)));
+ List<ConflictInfo> conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 1);
+
+ assertNotInList(schedules.get(0), conflicts);
+ assertFullConflict(schedules.get(1), conflicts);
+ assertPartialConflict(schedules.get(2), conflicts);
+ assertPartialConflict(schedules.get(3), conflicts);
+ assertNotInList(schedules.get(4), conflicts);
+ assertPartialConflict(schedules.get(5), conflicts);
+ assertNotInList(schedules.get(6), conflicts);
+ assertFullConflict(schedules.get(7), conflicts);
+ assertFullConflict(schedules.get(8), conflicts);
+ assertFullConflict(schedules.get(9), conflicts);
+ assertFullConflict(schedules.get(10), conflicts);
+
+ conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 2);
+
+ assertNotInList(schedules.get(0), conflicts);
+ assertNotInList(schedules.get(1), conflicts);
+ assertNotInList(schedules.get(2), conflicts);
+ assertNotInList(schedules.get(3), conflicts);
+ assertNotInList(schedules.get(4), conflicts);
+ assertNotInList(schedules.get(5), conflicts);
+ assertNotInList(schedules.get(6), conflicts);
+ assertFullConflict(schedules.get(7), conflicts);
+ assertFullConflict(schedules.get(8), conflicts);
+ assertFullConflict(schedules.get(9), conflicts);
+ assertPartialConflict(schedules.get(10), conflicts);
+
+ conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 3);
+
+ assertNotInList(schedules.get(0), conflicts);
+ assertNotInList(schedules.get(1), conflicts);
+ assertNotInList(schedules.get(2), conflicts);
+ assertNotInList(schedules.get(3), conflicts);
+ assertNotInList(schedules.get(4), conflicts);
+ assertNotInList(schedules.get(5), conflicts);
+ assertNotInList(schedules.get(6), conflicts);
+ assertNotInList(schedules.get(7), conflicts);
+ assertPartialConflict(schedules.get(8), conflicts);
+ assertNotInList(schedules.get(9), conflicts);
+ assertPartialConflict(schedules.get(10), conflicts);
+ }
+
+ private void assertNotInList(ScheduledRecording schedule, List<ConflictInfo> conflicts) {
+ for (ConflictInfo conflictInfo : conflicts) {
+ if (conflictInfo.schedule.equals(schedule)) {
+ fail(schedule + " conflicts with others.");
+ }
+ }
+ }
+
+ private void assertPartialConflict(ScheduledRecording schedule, List<ConflictInfo> conflicts) {
+ for (ConflictInfo conflictInfo : conflicts) {
+ if (conflictInfo.schedule.equals(schedule)) {
+ if (conflictInfo.partialConflict) {
+ return;
+ } else {
+ fail(schedule + " fully conflicts with others.");
+ }
+ }
+ }
+ fail(schedule + " doesn't conflict");
+ }
+
+ private void assertFullConflict(ScheduledRecording schedule, List<ConflictInfo> conflicts) {
+ for (ConflictInfo conflictInfo : conflicts) {
+ if (conflictInfo.schedule.equals(schedule)) {
+ if (!conflictInfo.partialConflict) {
+ return;
+ } else {
+ fail(schedule + " partially conflicts with others.");
+ }
+ }
+ }
+ fail(schedule + " doesn't conflict");
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/ScheduledRecordingTest.java b/tests/robotests/src/com/android/tv/dvr/ScheduledRecordingTest.java
new file mode 100644
index 0000000..8213fb3
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/ScheduledRecordingTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 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.dvr;
+
+import static com.android.tv.testing.dvr.RecordingTestUtils.createTestRecordingWithIdAndPeriod;
+import static com.android.tv.testing.dvr.RecordingTestUtils.normalizePriority;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Build;
+import android.util.Range;
+
+import com.android.tv.data.ChannelImpl;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.RecordingTestUtils;
+
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/** Tests for {@link ScheduledRecordingTest} */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class ScheduledRecordingTest {
+ private static final String INPUT_ID = "input_id";
+ private static final int CHANNEL_ID = 273;
+
+ @Test
+ public void testIsOverLapping() {
+ ScheduledRecording r =
+ createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L);
+ assertOverLapping(false, 1L, 9L, r);
+
+ assertOverLapping(true, 1L, 20L, r);
+ assertOverLapping(false, 1L, 10L, r);
+ assertOverLapping(true, 10L, 19L, r);
+ assertOverLapping(true, 10L, 20L, r);
+ assertOverLapping(true, 11L, 20L, r);
+ assertOverLapping(true, 11L, 21L, r);
+ assertOverLapping(false, 20L, 21L, r);
+
+ assertOverLapping(false, 21L, 29L, r);
+ }
+
+ @Test
+ public void testBuildProgram() {
+ Channel c = new ChannelImpl.Builder().build();
+ Program p = new ProgramImpl.Builder().build();
+ ScheduledRecording actual =
+ ScheduledRecording.builder(INPUT_ID, p).setChannelId(c.getId()).build();
+ assertWithMessage("type").that(actual.getType()).isEqualTo(ScheduledRecording.TYPE_PROGRAM);
+ }
+
+ @Test
+ public void testBuildTime() {
+ ScheduledRecording actual =
+ createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L);
+ assertWithMessage("type").that(actual.getType()).isEqualTo(ScheduledRecording.TYPE_TIMED);
+ }
+
+ @Test
+ public void testBuildFrom() {
+ ScheduledRecording expected =
+ createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L);
+ ScheduledRecording actual = ScheduledRecording.buildFrom(expected).build();
+ RecordingTestUtils.assertRecordingEquals(expected, actual);
+ }
+
+ @Test
+ public void testBuild_priority() {
+ ScheduledRecording a =
+ normalizePriority(
+ createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L));
+ ScheduledRecording b =
+ normalizePriority(
+ createTestRecordingWithIdAndPeriod(2, INPUT_ID, CHANNEL_ID, 10L, 20L));
+ ScheduledRecording c =
+ normalizePriority(
+ createTestRecordingWithIdAndPeriod(3, INPUT_ID, CHANNEL_ID, 10L, 20L));
+
+ // default priority
+ assertThat(sortByPriority(c, b, a)).containsExactly(a, b, c).inOrder();
+
+ // make A preferred over B
+ a = ScheduledRecording.buildFrom(a).setPriority(b.getPriority() + 2).build();
+ assertThat(sortByPriority(a, b, c)).containsExactly(b, c, a).inOrder();
+ }
+
+ public Collection<ScheduledRecording> sortByPriority(
+ ScheduledRecording a, ScheduledRecording b, ScheduledRecording c) {
+ List<ScheduledRecording> list = Arrays.asList(a, b, c);
+ Collections.sort(list, ScheduledRecording.PRIORITY_COMPARATOR);
+ return list;
+ }
+
+ private void assertOverLapping(boolean expected, long lower, long upper, ScheduledRecording r) {
+ assertWithMessage("isOverlapping(Range(" + lower + "," + upper + "), recording " + r)
+ .that(r.isOverLapping(new Range<>(lower, upper)))
+ .isEqualTo(expected);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/data/SeriesRecordingTest.java b/tests/robotests/src/com/android/tv/dvr/data/SeriesRecordingTest.java
new file mode 100644
index 0000000..f1cc148
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/data/SeriesRecordingTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 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.dvr.data;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Parcel;
+
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Program;
+import com.android.tv.testing.constants.ConfigConstants;
+
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link SeriesRecording}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class SeriesRecordingTest {
+ private static final String PROGRAM_TITLE = "MyProgram";
+ private static final long CHANNEL_ID = 123;
+ private static final long OTHER_CHANNEL_ID = 321;
+ private static final String SERIES_ID = "SERIES_ID";
+ private static final String OTHER_SERIES_ID = "OTHER_SERIES_ID";
+
+ private final SeriesRecording mBaseSeriesRecording =
+ new SeriesRecording.Builder()
+ .setTitle(PROGRAM_TITLE)
+ .setChannelId(CHANNEL_ID)
+ .setSeriesId(SERIES_ID)
+ .build();
+ private final SeriesRecording mSeriesRecordingSeason2 =
+ SeriesRecording.buildFrom(mBaseSeriesRecording).setStartFromSeason(2).build();
+ private final SeriesRecording mSeriesRecordingSeason2Episode5 =
+ SeriesRecording.buildFrom(mSeriesRecordingSeason2).setStartFromEpisode(5).build();
+ private final ProgramImpl mBaseProgram =
+ new ProgramImpl.Builder()
+ .setTitle(PROGRAM_TITLE)
+ .setChannelId(CHANNEL_ID)
+ .setSeriesId(SERIES_ID)
+ .build();
+
+ @Test
+ public void testParcelable() {
+ SeriesRecording r1 =
+ new SeriesRecording.Builder()
+ .setId(1)
+ .setChannelId(2)
+ .setPriority(3)
+ .setTitle("4")
+ .setDescription("5")
+ .setLongDescription("5-long")
+ .setSeriesId("6")
+ .setStartFromEpisode(7)
+ .setStartFromSeason(8)
+ .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL)
+ .setCanonicalGenreIds(new int[] {9, 10})
+ .setPosterUri("11")
+ .setPhotoUri("12")
+ .build();
+ Parcel p1 = Parcel.obtain();
+ Parcel p2 = Parcel.obtain();
+ try {
+ r1.writeToParcel(p1, 0);
+ byte[] bytes = p1.marshall();
+ p2.unmarshall(bytes, 0, bytes.length);
+ p2.setDataPosition(0);
+ SeriesRecording r2 = SeriesRecording.fromParcel(p2);
+ assertThat(r2).isEqualTo(r1);
+ } finally {
+ p1.recycle();
+ p2.recycle();
+ }
+ }
+
+ @Test
+ public void testDoesProgramMatch_simpleMatch() {
+ assertDoesProgramMatch(mBaseProgram, mBaseSeriesRecording, true);
+ }
+
+ @Test
+ public void testDoesProgramMatch_differentSeriesId() {
+ Program program =
+ new ProgramImpl.Builder(mBaseProgram).setSeriesId(OTHER_SERIES_ID).build();
+ assertDoesProgramMatch(program, mBaseSeriesRecording, false);
+ }
+
+ @Test
+ public void testDoesProgramMatch_differentChannel() {
+ Program program =
+ new ProgramImpl.Builder(mBaseProgram).setChannelId(OTHER_CHANNEL_ID).build();
+ assertDoesProgramMatch(program, mBaseSeriesRecording, false);
+ }
+
+ @Test
+ public void testDoesProgramMatch_startFromSeason2() {
+ ProgramImpl program = mBaseProgram;
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2, true);
+ program = new ProgramImpl.Builder(program).setSeasonNumber("1").build();
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2, false);
+ program = new ProgramImpl.Builder(program).setSeasonNumber("2").build();
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2, true);
+ program = new ProgramImpl.Builder(program).setSeasonNumber("3").build();
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2, true);
+ }
+
+ @Test
+ public void testDoesProgramMatch_startFromSeason2episode5() {
+ ProgramImpl program = mBaseProgram;
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true);
+ program = new ProgramImpl.Builder(program).setSeasonNumber("2").build();
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true);
+ program = new ProgramImpl.Builder(program).setEpisodeNumber("4").build();
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, false);
+ program = new ProgramImpl.Builder(program).setEpisodeNumber("5").build();
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true);
+ program = new ProgramImpl.Builder(program).setEpisodeNumber("6").build();
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true);
+ program =
+ new ProgramImpl.Builder(program).setSeasonNumber("3").setEpisodeNumber("1").build();
+ assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true);
+ }
+
+ private void assertDoesProgramMatch(
+ Program p, SeriesRecording seriesRecording, boolean expected) {
+ assertWithMessage(seriesRecording + " doesProgramMatch " + p)
+ .that(seriesRecording.matchProgram(p))
+ .isEqualTo(expected);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/provider/DvrDbSyncTest.java b/tests/robotests/src/com/android/tv/dvr/provider/DvrDbSyncTest.java
new file mode 100644
index 0000000..92b4755
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/provider/DvrDbSyncTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 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.dvr.provider;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Program;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.WritableDvrDataManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.util.concurrent.RoboExecutorService;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link com.android.tv.dvr.DvrScheduleManager} */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
+public class DvrDbSyncTest {
+ private static final String INPUT_ID = "input_id";
+ private static final long BASE_PROGRAM_ID = 1;
+ private static final long BASE_START_TIME_MS = 0;
+ private static final long BASE_END_TIME_MS = 1;
+ private static final String BASE_SEASON_NUMBER = "2";
+ private static final String BASE_EPISODE_NUMBER = "3";
+ private ProgramImpl baseProgram;
+ private ProgramImpl baseSeriesProgram;
+ private ScheduledRecording baseSchedule;
+ private ScheduledRecording baseSeriesSchedule;
+
+ private DvrDbSync mDbSync;
+ @Mock private DvrManager mDvrManager;
+ @Mock private WritableDvrDataManager mDataManager;
+ @Mock private ChannelDataManager mChannelDataManager;
+ @Mock private SeriesRecordingScheduler mSeriesRecordingScheduler;
+
+ @Before
+ public void setUp() {
+ // TODO(b/69843199): make these static finals
+ baseProgram =
+ new ProgramImpl.Builder()
+ .setId(BASE_PROGRAM_ID)
+ .setStartTimeUtcMillis(BASE_START_TIME_MS)
+ .setEndTimeUtcMillis(BASE_END_TIME_MS)
+ .build();
+ baseSeriesProgram =
+ new ProgramImpl.Builder()
+ .setId(BASE_PROGRAM_ID)
+ .setStartTimeUtcMillis(BASE_START_TIME_MS)
+ .setEndTimeUtcMillis(BASE_END_TIME_MS)
+ .setSeasonNumber(BASE_SEASON_NUMBER)
+ .setEpisodeNumber(BASE_EPISODE_NUMBER)
+ .build();
+ baseSchedule = ScheduledRecording.builder(INPUT_ID, baseProgram).build();
+ baseSeriesSchedule = ScheduledRecording.builder(INPUT_ID, baseSeriesProgram).build();
+
+ MockitoAnnotations.initMocks(this);
+ when(mChannelDataManager.isDbLoadFinished()).thenReturn(true);
+ when(mDvrManager.addSeriesRecording(any(), any(), anyInt()))
+ .thenReturn(SeriesRecording.builder(INPUT_ID, baseProgram).build());
+ mDbSync =
+ new DvrDbSync(
+ RuntimeEnvironment.application.getApplicationContext(),
+ mDataManager,
+ mChannelDataManager,
+ mDvrManager,
+ mSeriesRecordingScheduler,
+ new RoboExecutorService());
+ }
+
+ @Test
+ public void testHandleUpdateProgram_null() {
+ addSchedule(BASE_PROGRAM_ID, baseSchedule);
+ mDbSync.handleUpdateProgram(null, BASE_PROGRAM_ID);
+ verify(mDataManager).removeScheduledRecording(baseSchedule);
+ }
+
+ @Test
+ public void testHandleUpdateProgram_changeTimeNotStarted() {
+ addSchedule(BASE_PROGRAM_ID, baseSchedule);
+ long startTimeMs = BASE_START_TIME_MS + 1;
+ long endTimeMs = BASE_END_TIME_MS + 1;
+ Program program =
+ new ProgramImpl.Builder(baseProgram)
+ .setStartTimeUtcMillis(startTimeMs)
+ .setEndTimeUtcMillis(endTimeMs)
+ .build();
+ mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID);
+ assertUpdateScheduleCalled(program);
+ }
+
+ @Test
+ public void testHandleUpdateProgram_changeTimeInProgressNotCalled() {
+ addSchedule(
+ BASE_PROGRAM_ID,
+ ScheduledRecording.buildFrom(baseSchedule)
+ .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS)
+ .build());
+ long startTimeMs = BASE_START_TIME_MS + 1;
+ Program program =
+ new ProgramImpl.Builder(baseProgram).setStartTimeUtcMillis(startTimeMs).build();
+ mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID);
+ verify(mDataManager, never()).updateScheduledRecording(any());
+ }
+
+ @Test
+ public void testHandleUpdateProgram_changeSeason() {
+ addSchedule(BASE_PROGRAM_ID, baseSeriesSchedule);
+ String seasonNumber = BASE_SEASON_NUMBER + "1";
+ String episodeNumber = BASE_EPISODE_NUMBER + "1";
+ Program program =
+ new ProgramImpl.Builder(baseSeriesProgram)
+ .setSeasonNumber(seasonNumber)
+ .setEpisodeNumber(episodeNumber)
+ .build();
+ mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID);
+ assertUpdateScheduleCalled(program);
+ }
+
+ @Test
+ public void testHandleUpdateProgram_finished() {
+ addSchedule(
+ BASE_PROGRAM_ID,
+ ScheduledRecording.buildFrom(baseSeriesSchedule)
+ .setState(ScheduledRecording.STATE_RECORDING_FINISHED)
+ .build());
+ String seasonNumber = BASE_SEASON_NUMBER + "1";
+ String episodeNumber = BASE_EPISODE_NUMBER + "1";
+ Program program =
+ new ProgramImpl.Builder(baseSeriesProgram)
+ .setSeasonNumber(seasonNumber)
+ .setEpisodeNumber(episodeNumber)
+ .build();
+ mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID);
+ verify(mDataManager, never()).updateScheduledRecording(any());
+ }
+
+ private void addSchedule(long programId, ScheduledRecording schedule) {
+ when(mDataManager.getScheduledRecordingForProgramId(programId)).thenReturn(schedule);
+ }
+
+ private void assertUpdateScheduleCalled(Program program) {
+ verify(mDataManager)
+ .updateScheduledRecording(
+ eq(ScheduledRecording.builder(INPUT_ID, program).build()));
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java b/tests/robotests/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java
new file mode 100644
index 0000000..1244dc1
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 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.dvr.provider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build.VERSION_CODES;
+import com.android.tv.dvr.data.SeasonEpisodeNumber;
+import com.android.tv.testing.TestSingletonApp;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link EpisodicProgramLoadTask} */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(
+ sdk = VERSION_CODES.N,
+ application = TestSingletonApp.class
+)
+public class EpisodicProgramLoadTaskTest {
+ private static final long SERIES_RECORDING_ID1 = 1;
+ private static final long SERIES_RECORDING_ID2 = 2;
+ private static final String SEASON_NUMBER1 = "SEASON NUMBER1";
+ private static final String SEASON_NUMBER2 = "SEASON NUMBER2";
+ private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1";
+ private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2";
+
+ @Test
+ public void testEpisodeAlreadyScheduled_true() {
+ List<SeasonEpisodeNumber> seasonEpisodeNumbers = new ArrayList<>();
+ SeasonEpisodeNumber seasonEpisodeNumber =
+ new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1);
+ seasonEpisodeNumbers.add(seasonEpisodeNumber);
+ assertThat(seasonEpisodeNumbers)
+ .contains(
+ new SeasonEpisodeNumber(
+ SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1));
+ }
+
+ @Test
+ public void testEpisodeAlreadyScheduled_false() {
+ List<SeasonEpisodeNumber> seasonEpisodeNumbers = new ArrayList<>();
+ SeasonEpisodeNumber seasonEpisodeNumber =
+ new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1);
+ seasonEpisodeNumbers.add(seasonEpisodeNumber);
+ assertThat(seasonEpisodeNumbers)
+ .doesNotContain(
+ new SeasonEpisodeNumber(
+ SERIES_RECORDING_ID2, SEASON_NUMBER1, EPISODE_NUMBER1));
+ assertThat(seasonEpisodeNumbers)
+ .doesNotContain(
+ new SeasonEpisodeNumber(
+ SERIES_RECORDING_ID1, SEASON_NUMBER2, EPISODE_NUMBER1));
+ assertThat(seasonEpisodeNumbers)
+ .doesNotContain(
+ new SeasonEpisodeNumber(
+ SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER2));
+ }
+
+ @Test
+ public void testEpisodeAlreadyScheduled_null() {
+ List<SeasonEpisodeNumber> seasonEpisodeNumbers = new ArrayList<>();
+ SeasonEpisodeNumber seasonEpisodeNumber =
+ new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1);
+ seasonEpisodeNumbers.add(seasonEpisodeNumber);
+ assertThat(seasonEpisodeNumbers)
+ .doesNotContain(
+ new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, EPISODE_NUMBER1));
+ assertThat(seasonEpisodeNumbers)
+ .doesNotContain(
+ new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, null));
+ assertThat(seasonEpisodeNumbers)
+ .doesNotContain(new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, null));
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java
new file mode 100644
index 0000000..ade3ae8
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2016 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.dvr.recorder;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.media.tv.TvInputInfo;
+import android.os.Build;
+import android.os.Looper;
+import android.os.SystemClock;
+import com.android.tv.InputSessionManager;
+import com.android.tv.common.util.Clock;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.WritableDvrDataManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.recorder.InputTaskScheduler.RecordingTaskFactory;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.RecordingTestUtils;
+import com.android.tv.testing.fakes.FakeClock;
+import com.android.tv.testing.utils.TestUtils;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link InputTaskScheduler}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class InputTaskSchedulerTest {
+ private static final String INPUT_ID = "input_id";
+ private static final int CHANNEL_ID = 1;
+ private static final long LISTENER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1);
+ private static final int TUNER_COUNT_ONE = 1;
+ private static final int TUNER_COUNT_TWO = 2;
+ private static final long LOW_PRIORITY = 1;
+ private static final long HIGH_PRIORITY = 2;
+
+ private FakeClock mFakeClock;
+ private InputTaskScheduler mScheduler;
+ @Mock private DvrManager mDvrManager;
+ @Mock private WritableDvrDataManager mDataManager;
+ @Mock private InputSessionManager mSessionManager;
+ @Mock private AlarmManager mMockAlarmManager;
+ @Mock private ChannelDataManager mChannelDataManager;
+ private List<RecordingTask> mRecordingTasks;
+
+ @Before
+ public void setUp() throws Exception {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ mRecordingTasks = new ArrayList();
+ MockitoAnnotations.initMocks(this);
+ mFakeClock = FakeClock.createWithCurrentTime();
+ TvInputInfo input = createTvInputInfo(TUNER_COUNT_ONE);
+ mScheduler =
+ new InputTaskScheduler(
+ RuntimeEnvironment.application,
+ input,
+ Looper.myLooper(),
+ mChannelDataManager,
+ mDvrManager,
+ mDataManager,
+ mSessionManager,
+ mFakeClock,
+ new RecordingTaskFactory() {
+ @Override
+ public RecordingTask createRecordingTask(
+ ScheduledRecording scheduledRecording,
+ Channel channel,
+ DvrManager dvrManager,
+ InputSessionManager sessionManager,
+ WritableDvrDataManager dataManager,
+ Clock clock) {
+ RecordingTask task = mock(RecordingTask.class);
+ when(task.getPriority())
+ .thenReturn(scheduledRecording.getPriority());
+ when(task.getEndTimeMs())
+ .thenReturn(scheduledRecording.getEndTimeMs());
+ mRecordingTasks.add(task);
+ return task;
+ }
+ });
+ }
+
+ @Test
+ public void testAddSchedule_past() {
+ ScheduledRecording r =
+ RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, 0L, 1L);
+ when(mDataManager.getScheduledRecording(anyLong())).thenReturn(r);
+ mScheduler.handleAddSchedule(r);
+ mScheduler.handleBuildSchedule();
+ verify(mDataManager, timeout((int) LISTENER_TIMEOUT_MS).times(1))
+ .changeState(
+ any(ScheduledRecording.class),
+ eq(ScheduledRecording.STATE_RECORDING_FAILED),
+ eq(ScheduledRecording
+ .FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED));
+ }
+
+ @Test
+ public void testAddSchedule_start() {
+ mScheduler.handleAddSchedule(
+ RecordingTestUtils.createTestRecordingWithPeriod(
+ INPUT_ID,
+ CHANNEL_ID,
+ mFakeClock.currentTimeMillis(),
+ mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)));
+ mScheduler.handleBuildSchedule();
+ verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start();
+ }
+
+ @Test
+ public void testAddSchedule_consecutiveNoStop() {
+ long startTimeMs = mFakeClock.currentTimeMillis();
+ long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1);
+ long id = 0;
+ mScheduler.handleAddSchedule(
+ RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(
+ ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs));
+ mScheduler.handleBuildSchedule();
+ startTimeMs = endTimeMs;
+ endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1);
+ mScheduler.handleAddSchedule(
+ RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(
+ ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs));
+ mScheduler.handleBuildSchedule();
+ verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start();
+ // The first schedule should not be stopped because the second one should wait for the end
+ // of the first schedule.
+ SystemClock.sleep(LISTENER_TIMEOUT_MS);
+ verify(mRecordingTasks.get(0), never()).stop();
+ }
+
+ @Test
+ public void testAddSchedule_consecutiveNoFail() {
+ long startTimeMs = mFakeClock.currentTimeMillis();
+ long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1);
+ long id = 0;
+ when(mDataManager.getScheduledRecording(anyLong()))
+ .thenReturn(ScheduledRecording.builder(INPUT_ID, CHANNEL_ID, 0L, 0L).build());
+ mScheduler.handleAddSchedule(
+ RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(
+ ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs));
+ mScheduler.handleBuildSchedule();
+ startTimeMs = endTimeMs;
+ endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1);
+ mScheduler.handleAddSchedule(
+ RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(
+ ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs));
+ mScheduler.handleBuildSchedule();
+ verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start();
+ SystemClock.sleep(LISTENER_TIMEOUT_MS);
+ verify(mRecordingTasks.get(0), never()).stop();
+ // The second schedule should not fail because it can starts after the first one finishes.
+ SystemClock.sleep(LISTENER_TIMEOUT_MS);
+ verify(mDataManager, never())
+ .changeState(
+ any(ScheduledRecording.class),
+ eq(ScheduledRecording.STATE_RECORDING_FAILED));
+ }
+
+ @Test
+ public void testAddSchedule_consecutiveUseLessSession() throws Exception {
+ TvInputInfo input = createTvInputInfo(TUNER_COUNT_TWO);
+ mScheduler.updateTvInputInfo(input);
+ long startTimeMs = mFakeClock.currentTimeMillis();
+ long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1);
+ long id = 0;
+ mScheduler.handleAddSchedule(
+ RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(
+ ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs));
+ mScheduler.handleBuildSchedule();
+ startTimeMs = endTimeMs;
+ endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1);
+ mScheduler.handleAddSchedule(
+ RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(
+ ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs));
+ mScheduler.handleBuildSchedule();
+ verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start();
+ SystemClock.sleep(LISTENER_TIMEOUT_MS);
+ verify(mRecordingTasks.get(0), never()).stop();
+ // The second schedule should wait until the first one finishes rather than creating a new
+ // session even though there are available tuners.
+ assertTrue(mRecordingTasks.size() == 1);
+ }
+
+ @Test
+ public void testUpdateSchedule_noCancel() {
+ ScheduledRecording r =
+ RecordingTestUtils.createTestRecordingWithPeriod(
+ INPUT_ID,
+ CHANNEL_ID,
+ mFakeClock.currentTimeMillis(),
+ mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
+ mScheduler.handleAddSchedule(r);
+ mScheduler.handleBuildSchedule();
+ mScheduler.handleUpdateSchedule(r);
+ SystemClock.sleep(LISTENER_TIMEOUT_MS);
+ verify(mRecordingTasks.get(0), never()).cancel();
+ }
+
+ @Test
+ public void testUpdateSchedule_cancel() {
+ ScheduledRecording r =
+ RecordingTestUtils.createTestRecordingWithPeriod(
+ INPUT_ID,
+ CHANNEL_ID,
+ mFakeClock.currentTimeMillis(),
+ mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(2));
+ mScheduler.handleAddSchedule(r);
+ mScheduler.handleBuildSchedule();
+ mScheduler.handleUpdateSchedule(
+ ScheduledRecording.buildFrom(r)
+ .setStartTimeMs(mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))
+ .build());
+ verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).cancel();
+ }
+
+ private TvInputInfo createTvInputInfo(int tunerCount) throws Exception {
+ return TestUtils.createTvInputInfo(null, null, null, 0, false, true, tunerCount);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/RecordingTaskTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/RecordingTaskTest.java
new file mode 100644
index 0000000..7a62563
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/recorder/RecordingTaskTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 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.dvr.recorder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import com.android.tv.InputSessionManager;
+import com.android.tv.InputSessionManager.RecordingSession;
+import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.feature.TestableFeature;
+import com.android.tv.data.ChannelImpl;
+import com.android.tv.data.api.Channel;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.recorder.RecordingTask.State;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.DvrDataManagerInMemoryImpl;
+import com.android.tv.testing.dvr.RecordingTestUtils;
+import com.android.tv.testing.fakes.FakeClock;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link RecordingTask}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class RecordingTaskTest {
+ private static final long DURATION = TimeUnit.MINUTES.toMillis(30);
+ private static final long START_OFFSET_MS = RecordingScheduler.MS_TO_WAKE_BEFORE_START;
+ private static final String INPUT_ID = "input_id";
+ private static final int CHANNEL_ID = 273;
+
+ private FakeClock mFakeClock;
+ private DvrDataManagerInMemoryImpl mDataManager;
+ @Mock Handler mMockHandler;
+ @Mock DvrManager mDvrManager;
+ @Mock InputSessionManager mMockSessionManager;
+ @Mock RecordingSession mMockRecordingSession;
+ private final TestableFeature mDvrFeature = CommonFeatures.DVR;
+
+ @Before
+ public void setUp() {
+ mDvrFeature.enableForTest();
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ MockitoAnnotations.initMocks(this);
+ mFakeClock = FakeClock.createWithCurrentTime();
+ mDataManager = new DvrDataManagerInMemoryImpl(RuntimeEnvironment.application, mFakeClock);
+ }
+
+ @After
+ public void tearDown() {
+ mDvrFeature.resetForTests();
+ }
+
+ @Test
+ public void testHandle_init() {
+ Channel channel = createTestChannel();
+ ScheduledRecording r = createRecording(channel);
+ RecordingTask task = createRecordingTask(r, channel);
+ String inputId = channel.getInputId();
+ when(mMockSessionManager.createRecordingSession(
+ eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong()))
+ .thenReturn(mMockRecordingSession);
+ when(mMockHandler.sendMessageAtTime(any(), anyLong())).thenReturn(true);
+ assertTrue(task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE)));
+ assertEquals(State.CONNECTION_PENDING, task.getState());
+ verify(mMockSessionManager)
+ .createRecordingSession(
+ eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong());
+ verify(mMockRecordingSession).tune(eq(inputId), eq(channel.getUri()));
+ verifyNoMoreInteractions(mMockHandler, mMockRecordingSession, mMockSessionManager);
+ }
+
+ private static Channel createTestChannel() {
+ return new ChannelImpl.Builder()
+ .setInputId(INPUT_ID)
+ .setId(CHANNEL_ID)
+ .setDisplayName("Test Ch " + CHANNEL_ID)
+ .build();
+ }
+
+ @Test
+ public void testOnConnected() {
+ Channel channel = createTestChannel();
+ ScheduledRecording r = createRecording(channel);
+ mDataManager.addScheduledRecording(r);
+ RecordingTask task = createRecordingTask(r, channel);
+ String inputId = channel.getInputId();
+ when(mMockSessionManager.createRecordingSession(
+ eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong()))
+ .thenReturn(mMockRecordingSession);
+ when(mMockHandler.sendEmptyMessageDelayed(anyInt(), anyLong())).thenReturn(true);
+ task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE));
+ task.onTuned(channel.getUri());
+ assertEquals(State.CONNECTED, task.getState());
+ }
+
+ private ScheduledRecording createRecording(Channel c) {
+ long startTime = mFakeClock.currentTimeMillis() + START_OFFSET_MS;
+ long endTime = startTime + DURATION;
+ return RecordingTestUtils.createTestRecordingWithPeriod(
+ c.getInputId(), c.getId(), startTime, endTime);
+ }
+
+ private RecordingTask createRecordingTask(ScheduledRecording r, Channel channel) {
+ RecordingTask recordingTask =
+ new RecordingTask(
+ RuntimeEnvironment.application,
+ r,
+ channel,
+ mDvrManager,
+ mMockSessionManager,
+ mDataManager,
+ mFakeClock);
+ recordingTask.setHandler(mMockHandler);
+ return recordingTask;
+ }
+
+ private Message createMessage(int what) {
+ Message msg = new Message();
+ msg.setTarget(mMockHandler);
+ msg.what = what;
+ return msg;
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java
new file mode 100644
index 0000000..9381de2
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 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.dvr.recorder;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Build;
+import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.feature.TestableFeature;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.DvrDataManagerInMemoryImpl;
+import com.android.tv.testing.dvr.RecordingTestUtils;
+import com.android.tv.testing.fakes.FakeClock;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link ScheduledProgramReaper}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class ScheduledProgramReaperTest {
+ private static final String INPUT_ID = "input_id";
+ private static final int CHANNEL_ID = 273;
+ private static final long DURATION = TimeUnit.HOURS.toMillis(1);
+
+ private ScheduledProgramReaper mReaper;
+ private FakeClock mFakeClock;
+ private DvrDataManagerInMemoryImpl mDvrDataManager;
+ @Mock private DvrManager mDvrManager;
+ private final TestableFeature mDvrFeature = CommonFeatures.DVR;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDvrFeature.enableForTest();
+ mFakeClock = FakeClock.createWithTimeOne();
+ mDvrDataManager =
+ new DvrDataManagerInMemoryImpl(RuntimeEnvironment.application, mFakeClock);
+ mReaper = new ScheduledProgramReaper(mDvrDataManager, mFakeClock);
+ }
+
+ @After
+ public void tearDown() {
+ mDvrFeature.resetForTests();
+ }
+
+ @Test
+ public void testRun_noRecordings() {
+ assertTrue(mDvrDataManager.getAllScheduledRecordings().isEmpty());
+ mReaper.run();
+ assertTrue(mDvrDataManager.getAllScheduledRecordings().isEmpty());
+ }
+
+ @Test
+ public void testRun_oneRecordingsTomorrow() {
+ ScheduledRecording recording = addNewScheduledRecordingForTomorrow();
+ assertThat(mDvrDataManager.getAllScheduledRecordings()).containsExactly(recording);
+ mReaper.run();
+ assertThat(mDvrDataManager.getAllScheduledRecordings()).containsExactly(recording);
+ }
+
+ @Test
+ public void testRun_oneRecordingsStarted() {
+ ScheduledRecording recording = addNewScheduledRecordingForTomorrow();
+ assertThat(mDvrDataManager.getAllScheduledRecordings()).containsExactly(recording);
+ mFakeClock.increment(TimeUnit.DAYS);
+ mReaper.run();
+ assertThat(mDvrDataManager.getAllScheduledRecordings()).containsExactly(recording);
+ }
+
+ @Test
+ public void testRun_oneRecordingsFinished() {
+ ScheduledRecording recording = addNewScheduledRecordingForTomorrow();
+ assertThat(mDvrDataManager.getAllScheduledRecordings()).containsExactly(recording);
+ mFakeClock.increment(TimeUnit.DAYS);
+ mFakeClock.increment(TimeUnit.MINUTES, 2);
+ mReaper.run();
+ assertThat(mDvrDataManager.getAllScheduledRecordings()).containsExactly(recording);
+ }
+
+ @Test
+ public void testRun_oneRecordingsExpired() {
+ ScheduledRecording recording = addNewScheduledRecordingForTomorrow();
+ assertThat(mDvrDataManager.getAllScheduledRecordings()).containsExactly(recording);
+ mFakeClock.increment(TimeUnit.DAYS, 1 + ScheduledProgramReaper.DAYS);
+ mFakeClock.increment(TimeUnit.MILLISECONDS, DURATION);
+ // After the cutoff and enough so we can see on the clock
+ mFakeClock.increment(TimeUnit.SECONDS, 1);
+
+ mReaper.run();
+ assertTrue(
+ "Recordings after reaper at "
+ + CommonUtils.toIsoDateTimeString(mFakeClock.currentTimeMillis()),
+ mDvrDataManager.getAllScheduledRecordings().isEmpty());
+ }
+
+ private ScheduledRecording addNewScheduledRecordingForTomorrow() {
+ long startTime = mFakeClock.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
+ ScheduledRecording recording =
+ RecordingTestUtils.createTestRecordingWithPeriod(
+ INPUT_ID, CHANNEL_ID, startTime, startTime + DURATION);
+ return mDvrDataManager.addScheduledRecordingInternal(
+ ScheduledRecording.buildFrom(recording)
+ .setState(ScheduledRecording.STATE_RECORDING_FINISHED)
+ .build());
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java
new file mode 100644
index 0000000..3060c47
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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.dvr.recorder;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.os.Build;
+import android.os.Looper;
+import com.android.tv.InputSessionManager;
+import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.feature.TestableFeature;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.DvrDataManagerInMemoryImpl;
+import com.android.tv.testing.dvr.RecordingTestUtils;
+import com.android.tv.testing.fakes.FakeClock;
+import com.android.tv.util.TvInputManagerHelper;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link RecordingScheduler}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class SchedulerTest {
+ private static final String INPUT_ID = "input_id";
+ private static final int CHANNEL_ID = 273;
+
+ private FakeClock mFakeClock;
+ private DvrDataManagerInMemoryImpl mDataManager;
+ private RecordingScheduler mScheduler;
+ @Mock DvrManager mDvrManager;
+ @Mock InputSessionManager mSessionManager;
+ @Mock AlarmManager mMockAlarmManager;
+ @Mock ChannelDataManager mChannelDataManager;
+ @Mock TvInputManagerHelper mInputManager;
+ private final TestableFeature mDvrFeature = CommonFeatures.DVR;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDvrFeature.enableForTest();
+ mFakeClock = FakeClock.createWithCurrentTime();
+ mDataManager = new DvrDataManagerInMemoryImpl(RuntimeEnvironment.application, mFakeClock);
+ Mockito.when(mChannelDataManager.isDbLoadFinished()).thenReturn(true);
+ mScheduler =
+ new RecordingScheduler(
+ Looper.myLooper(),
+ mDvrManager,
+ mSessionManager,
+ mDataManager,
+ mChannelDataManager,
+ mInputManager,
+ RuntimeEnvironment.application,
+ mFakeClock,
+ mMockAlarmManager);
+ }
+
+ @After
+ public void tearDown() {
+ mDvrFeature.resetForTests();
+ }
+
+ @Test
+ public void testUpdate_none() {
+ mScheduler.updateAndStartServiceIfNeeded();
+ verifyZeroInteractions(mMockAlarmManager);
+ }
+
+ @Test
+ public void testUpdate_nextIn12Hours() {
+ long now = mFakeClock.currentTimeMillis();
+ long startTime = now + TimeUnit.HOURS.toMillis(12);
+ ScheduledRecording r =
+ RecordingTestUtils.createTestRecordingWithPeriod(
+ INPUT_ID, CHANNEL_ID, startTime, startTime + TimeUnit.HOURS.toMillis(1));
+ mDataManager.addScheduledRecording(r);
+ verify(mMockAlarmManager)
+ .setExactAndAllowWhileIdle(
+ eq(AlarmManager.RTC_WAKEUP),
+ eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START),
+ any(PendingIntent.class));
+ Mockito.reset(mMockAlarmManager);
+ mScheduler.updateAndStartServiceIfNeeded();
+ verify(mMockAlarmManager)
+ .setExactAndAllowWhileIdle(
+ eq(AlarmManager.RTC_WAKEUP),
+ eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START),
+ any(PendingIntent.class));
+ }
+
+ @Test
+ public void testStartsWithin() {
+ long now = mFakeClock.currentTimeMillis();
+ long startTime = now + 3;
+ ScheduledRecording r =
+ RecordingTestUtils.createTestRecordingWithPeriod(
+ INPUT_ID, CHANNEL_ID, startTime, startTime + 100);
+ assertFalse(mScheduler.startsWithin(r, 2));
+ assertTrue(mScheduler.startsWithin(r, 3));
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java
new file mode 100644
index 0000000..f8d6519
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 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.dvr.recorder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import android.util.LongSparseArray;
+
+import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.feature.TestableFeature;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Program;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.dvr.DvrDataManagerInMemoryImpl;
+import com.android.tv.testing.fakes.FakeClock;
+
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Tests for {@link SeriesRecordingScheduler} */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.N, application = TestSingletonApp.class)
+public class SeriesRecordingSchedulerTest {
+ private static final String PROGRAM_TITLE = "MyProgram";
+ private static final long CHANNEL_ID = 123;
+ private static final long SERIES_RECORDING_ID1 = 1;
+ private static final String SERIES_ID = "SERIES_ID";
+ private static final String SEASON_NUMBER1 = "SEASON NUMBER1";
+ private static final String SEASON_NUMBER2 = "SEASON NUMBER2";
+ private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1";
+ private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2";
+
+ private final SeriesRecording mBaseSeriesRecording =
+ new SeriesRecording.Builder()
+ .setTitle(PROGRAM_TITLE)
+ .setChannelId(CHANNEL_ID)
+ .setSeriesId(SERIES_ID)
+ .build();
+ private final ProgramImpl mBaseProgram =
+ new ProgramImpl.Builder()
+ .setTitle(PROGRAM_TITLE)
+ .setChannelId(CHANNEL_ID)
+ .setSeriesId(SERIES_ID)
+ .build();
+ private final TestableFeature mDvrFeature = CommonFeatures.DVR;
+
+ private DvrDataManagerInMemoryImpl mDataManager;
+
+ @Before
+ public void setUp() {
+ mDvrFeature.enableForTest();
+ FakeClock fakeClock = FakeClock.createWithCurrentTime();
+ mDataManager = new DvrDataManagerInMemoryImpl(RuntimeEnvironment.application, fakeClock);
+ }
+
+ @After
+ public void tearDown() {
+ mDvrFeature.resetForTests();
+ }
+
+ @Test
+ public void testPickOneProgramPerEpisode_onePerEpisode() {
+ SeriesRecording seriesRecording =
+ SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build();
+ mDataManager.addSeriesRecording(seriesRecording);
+ List<Program> programs = new ArrayList<>();
+ Program program1 =
+ new ProgramImpl.Builder(mBaseProgram)
+ .setSeasonNumber(SEASON_NUMBER1)
+ .setEpisodeNumber(EPISODE_NUMBER1)
+ .build();
+ programs.add(program1);
+ Program program2 =
+ new ProgramImpl.Builder(mBaseProgram)
+ .setSeasonNumber(SEASON_NUMBER2)
+ .setEpisodeNumber(EPISODE_NUMBER2)
+ .build();
+ programs.add(program2);
+ LongSparseArray<List<Program>> result =
+ SeriesRecordingScheduler.pickOneProgramPerEpisode(
+ mDataManager, Collections.singletonList(seriesRecording), programs);
+ assertThat(result.get(SERIES_RECORDING_ID1)).containsExactly(program1, program2);
+ }
+
+ @Test
+ public void testPickOneProgramPerEpisode_manyPerEpisode() {
+ SeriesRecording seriesRecording =
+ SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build();
+ mDataManager.addSeriesRecording(seriesRecording);
+ List<Program> programs = new ArrayList<>();
+ ProgramImpl program1 =
+ new ProgramImpl.Builder(mBaseProgram)
+ .setSeasonNumber(SEASON_NUMBER1)
+ .setEpisodeNumber(EPISODE_NUMBER1)
+ .setStartTimeUtcMillis(0)
+ .build();
+ programs.add(program1);
+ Program program2 = new ProgramImpl.Builder(program1).setStartTimeUtcMillis(1).build();
+ programs.add(program2);
+ Program program3 =
+ new ProgramImpl.Builder(mBaseProgram)
+ .setSeasonNumber(SEASON_NUMBER2)
+ .setEpisodeNumber(EPISODE_NUMBER2)
+ .build();
+ programs.add(program3);
+ Program program4 = new ProgramImpl.Builder(program1).setStartTimeUtcMillis(1).build();
+ programs.add(program4);
+ LongSparseArray<List<Program>> result =
+ SeriesRecordingScheduler.pickOneProgramPerEpisode(
+ mDataManager, Collections.singletonList(seriesRecording), programs);
+ assertThat(result.get(SERIES_RECORDING_ID1)).containsExactly(program1, program3);
+ }
+
+ @Test
+ public void testPickOneProgramPerEpisode_nullEpisode() {
+ SeriesRecording seriesRecording =
+ SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build();
+ mDataManager.addSeriesRecording(seriesRecording);
+ List<Program> programs = new ArrayList<>();
+ Program program1 = new ProgramImpl.Builder(mBaseProgram).setStartTimeUtcMillis(0).build();
+ programs.add(program1);
+ Program program2 = new ProgramImpl.Builder(mBaseProgram).setStartTimeUtcMillis(1).build();
+ programs.add(program2);
+ LongSparseArray<List<Program>> result =
+ SeriesRecordingScheduler.pickOneProgramPerEpisode(
+ mDataManager, Collections.singletonList(seriesRecording), programs);
+ assertThat(result.get(SERIES_RECORDING_ID1)).containsExactly(program1, program2);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java b/tests/robotests/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java
new file mode 100644
index 0000000..94ce184
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 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.dvr.ui;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.leanback.widget.ClassPresenterSelector;
+import androidx.leanback.widget.ObjectAdapter;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link SortedArrayAdapter}. */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class SortedArrayAdapterTest {
+ public static final TestData P1 = TestData.create(1, "c");
+ public static final TestData P2 = TestData.create(2, "b");
+ public static final TestData P3 = TestData.create(3, "a");
+ public static final TestData EXTRA = TestData.create(4, "k");
+ private TestSortedArrayAdapter mAdapter;
+
+ @Before
+ public void setUp() {
+ mAdapter = new TestSortedArrayAdapter(Integer.MAX_VALUE, null);
+ }
+
+ @Test
+ public void testContents_empty() {
+ assertEmpty();
+ }
+
+ @Test
+ public void testAdd_one() {
+ mAdapter.add(P1);
+ assertNotEmpty();
+ assertContentsInOrder(mAdapter, P1);
+ }
+
+ @Test
+ public void testAdd_two() {
+ mAdapter.add(P1);
+ mAdapter.add(P2);
+ assertNotEmpty();
+ assertContentsInOrder(mAdapter, P2, P1);
+ }
+
+ @Test
+ public void testSetInitialItems_two() {
+ mAdapter.setInitialItems(Arrays.asList(P1, P2));
+ assertNotEmpty();
+ assertContentsInOrder(mAdapter, P2, P1);
+ }
+
+ @Test
+ public void testMaxInitialCount() {
+ mAdapter = new TestSortedArrayAdapter(1, null);
+ mAdapter.setInitialItems(Arrays.asList(P1, P2));
+ assertNotEmpty();
+ assertThat(mAdapter.size()).isEqualTo(1);
+ assertThat(mAdapter.get(0)).isEqualTo(P2);
+ }
+
+ @Test
+ public void testExtraItem() {
+ mAdapter = new TestSortedArrayAdapter(Integer.MAX_VALUE, EXTRA);
+ mAdapter.setInitialItems(Arrays.asList(P1, P2));
+ assertNotEmpty();
+ assertThat(mAdapter.size()).isEqualTo(3);
+ assertThat(mAdapter.get(0)).isEqualTo(P2);
+ assertThat(mAdapter.get(2)).isEqualTo(EXTRA);
+ mAdapter.remove(P2);
+ mAdapter.remove(P1);
+ assertThat(mAdapter.size()).isEqualTo(1);
+ assertThat(mAdapter.get(0)).isEqualTo(EXTRA);
+ }
+
+ @Test
+ public void testExtraItemWithMaxCount() {
+ mAdapter = new TestSortedArrayAdapter(1, EXTRA);
+ mAdapter.setInitialItems(Arrays.asList(P1, P2));
+ assertNotEmpty();
+ assertThat(mAdapter.size()).isEqualTo(2);
+ assertThat(mAdapter.get(0)).isEqualTo(P2);
+ assertThat(mAdapter.get(1)).isEqualTo(EXTRA);
+ mAdapter.remove(P2);
+ assertThat(mAdapter.size()).isEqualTo(1);
+ assertThat(mAdapter.get(0)).isEqualTo(EXTRA);
+ }
+
+ @Test
+ public void testRemove() {
+ mAdapter.add(P1);
+ mAdapter.add(P2);
+ assertNotEmpty();
+ assertContentsInOrder(mAdapter, P2, P1);
+ mAdapter.remove(P3);
+ assertContentsInOrder(mAdapter, P2, P1);
+ mAdapter.remove(P2);
+ assertContentsInOrder(mAdapter, P1);
+ mAdapter.remove(P1);
+ assertEmpty();
+ mAdapter.add(P1);
+ mAdapter.add(P2);
+ mAdapter.add(P3);
+ assertContentsInOrder(mAdapter, P3, P2, P1);
+ mAdapter.removeItems(0, 2);
+ assertContentsInOrder(mAdapter, P1);
+ mAdapter.add(P2);
+ mAdapter.add(P3);
+ mAdapter.addExtraItem(EXTRA);
+ assertContentsInOrder(mAdapter, P3, P2, P1, EXTRA);
+ mAdapter.removeItems(1, 1);
+ assertContentsInOrder(mAdapter, P3, P1, EXTRA);
+ mAdapter.removeItems(1, 2);
+ assertContentsInOrder(mAdapter, P3);
+ mAdapter.addExtraItem(EXTRA);
+ mAdapter.addExtraItem(P2);
+ mAdapter.add(P1);
+ assertContentsInOrder(mAdapter, P3, P1, EXTRA, P2);
+ mAdapter.removeItems(1, 2);
+ assertContentsInOrder(mAdapter, P3, P2);
+ mAdapter.add(P1);
+ assertContentsInOrder(mAdapter, P3, P1, P2);
+ }
+
+ @Test
+ public void testReplace() {
+ mAdapter.add(P1);
+ mAdapter.add(P2);
+ assertNotEmpty();
+ assertContentsInOrder(mAdapter, P2, P1);
+ mAdapter.replace(1, P3);
+ assertContentsInOrder(mAdapter, P3, P2);
+ mAdapter.replace(0, P1);
+ assertContentsInOrder(mAdapter, P2, P1);
+ mAdapter.addExtraItem(EXTRA);
+ assertContentsInOrder(mAdapter, P2, P1, EXTRA);
+ mAdapter.replace(2, P3);
+ assertContentsInOrder(mAdapter, P2, P1, P3);
+ }
+
+ @Test
+ public void testChange_sorting() {
+ TestData p2_changed = TestData.create(2, "z changed");
+ mAdapter.add(P1);
+ mAdapter.add(P2);
+ assertNotEmpty();
+ assertContentsInOrder(mAdapter, P2, P1);
+ mAdapter.change(p2_changed);
+ assertContentsInOrder(mAdapter, P1, p2_changed);
+ }
+
+ @Test
+ public void testChange_new() {
+ mAdapter.change(P1);
+ assertNotEmpty();
+ assertContentsInOrder(mAdapter, P1);
+ }
+
+ private void assertEmpty() {
+ assertWithMessage("empty").that(mAdapter.isEmpty()).isTrue();
+ }
+
+ private void assertNotEmpty() {
+ assertWithMessage("empty").that(mAdapter.isEmpty()).isFalse();
+ }
+
+ private static void assertContentsInOrder(ObjectAdapter adapter, Object... contents) {
+ int ex = contents.length;
+ assertWithMessage("size").that(adapter.size()).isEqualTo(ex);
+ for (int i = 0; i < ex; i++) {
+ assertWithMessage("element " + 1).that(adapter.get(i)).isEqualTo(contents[i]);
+ }
+ }
+
+ private static class TestData {
+ @Override
+ public String toString() {
+ return "TestData[" + mId + "]{" + mText + '}';
+ }
+
+ static TestData create(long first, String text) {
+ return new TestData(first, text);
+ }
+
+ private final long mId;
+ private final String mText;
+
+ private TestData(long id, String second) {
+ this.mId = id;
+ this.mText = second;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TestData)) return false;
+ TestData that = (TestData) o;
+ return mId == that.mId && Objects.equals(mText, that.mText);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mText);
+ }
+ }
+
+ private static class TestSortedArrayAdapter extends SortedArrayAdapter<TestData> {
+
+ private static final Comparator<TestData> TEXT_COMPARATOR =
+ new Comparator<TestData>() {
+ @Override
+ public int compare(TestData lhs, TestData rhs) {
+ return lhs.mText.compareTo(rhs.mText);
+ }
+ };
+
+ TestSortedArrayAdapter(int maxInitialCount, Object extra) {
+ super(new ClassPresenterSelector(), TEXT_COMPARATOR, maxInitialCount);
+ if (extra != null) {
+ addExtraItem((TestData) extra);
+ }
+ }
+
+ @Override
+ protected long getId(TestData item) {
+ return item.mId;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/ui/browse/DvrBrowseFragmentTest.java b/tests/robotests/src/com/android/tv/dvr/ui/browse/DvrBrowseFragmentTest.java
new file mode 100644
index 0000000..9b44117
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/ui/browse/DvrBrowseFragmentTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.dvr.ui.browse;
+
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.testing.ComparatorTester;
+import com.android.tv.testing.constants.ConfigConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link DvrBrowseFragment}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class DvrBrowseFragmentTest {
+
+ @Test
+ public void testRecentRowComparator_scheduledRecordings_latestFirst() {
+ ComparatorTester<Object> comparatorTester =
+ ComparatorTester.withoutEqualsTest(DvrBrowseFragment.RECENT_ROW_COMPARATOR);
+ // priority (highest to lowest): start time, class, ID
+ comparatorTester.addComparableGroup(buildRecordedProgramForTest(2, 120, 150));
+ comparatorTester.addComparableGroup(buildRecordedProgramForTest(1, 120, 150));
+ comparatorTester.addComparableGroup(buildScheduledRecordingForTest(1, 120, 150));
+ comparatorTester.addComparableGroup(buildScheduledRecordingForTest(2, 120, 150));
+ comparatorTester.addComparableGroup(buildRecordedProgramForTest(4, 100, 200));
+ comparatorTester.addComparableGroup(buildRecordedProgramForTest(3, 100, 200));
+ comparatorTester.addComparableGroup(buildScheduledRecordingForTest(3, 100, 200));
+ comparatorTester.addComparableGroup(buildScheduledRecordingForTest(4, 100, 200));
+ comparatorTester.addComparableGroup(new Object(), Long.valueOf("777"), "string");
+ comparatorTester.test();
+ }
+
+ private ScheduledRecording buildScheduledRecordingForTest(long id, long start, long end) {
+ return ScheduledRecording.builder("test", 1, start, end).setId(id).build();
+ }
+
+ private RecordedProgram buildRecordedProgramForTest(long id, long start, long end) {
+ return RecordedProgram.builder()
+ .setId(id)
+ .setInputId("test")
+ .setStartTimeUtcMillis(start)
+ .setEndTimeUtcMillis(end)
+ .build();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapterTest.java b/tests/robotests/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapterTest.java
new file mode 100644
index 0000000..a317a76
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapterTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2018 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.dvr.ui.list;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.leanback.widget.ClassPresenterSelector;
+import com.android.tv.common.flags.impl.DefaultUiFlags;
+import com.android.tv.common.util.Clock;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.dvr.DvrDataManagerInMemoryImpl;
+import com.android.tv.testing.fakes.FakeClock;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link DvrHistoryRowAdapter}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
+public class DvrHistoryRowAdapterTest {
+
+ private static final ScheduledRecording SCHEDULE_1 =
+ buildScheduledRecordingForTest(
+ 2,
+ 1518249600000L, // 2/10/2018 0:00 PST
+ 1518250600000L,
+ ScheduledRecording.STATE_RECORDING_FAILED);
+ private static final ScheduledRecording SCHEDULE_1_COPY =
+ buildScheduledRecordingForTest(
+ 3,
+ 1518249600000L, // 2/10/2018 0:00 PST
+ 1518250600000L,
+ ScheduledRecording.STATE_RECORDING_FAILED);
+ private static final ScheduledRecording SCHEDULE_2 =
+ buildScheduledRecordingForTest(
+ 4,
+ 1518595200000L, // 2/14/2018 0:00 PST
+ 1518596200000L,
+ ScheduledRecording.STATE_RECORDING_FAILED);
+ private static final ScheduledRecording SCHEDULE_2_COPY =
+ buildScheduledRecordingForTest(
+ 5,
+ 1518595200000L, // 2/14/2018 0:00 PST
+ 1518596200000L,
+ ScheduledRecording.STATE_RECORDING_FAILED);
+
+ private static RecordedProgram sRecordedProgram;
+ private static final long FAKE_CURRENT_TIME = 1518764400000L; // 2/15/2018 23:00 PST
+
+ private DvrHistoryRowAdapter mDvrHistoryRowAdapter;
+ private DvrDataManagerInMemoryImpl mDvrDataManager;
+
+ @Before
+ public void setUp() {
+ sRecordedProgram =
+ buildRecordedProgramForTest(
+ 6,
+ 1518249600000L, // 2/10/2018 0:00 PST
+ 1518250600000L);
+
+ TestSingletonApp app = (TestSingletonApp) RuntimeEnvironment.application;
+ Clock clock = FakeClock.createWithTime(FAKE_CURRENT_TIME);
+
+ mDvrDataManager = new DvrDataManagerInMemoryImpl(app, clock);
+ app.mDvrDataManager = mDvrDataManager;
+ // keep the original IDs instead of creating a new one.
+ mDvrDataManager.addScheduledRecording(
+ true, SCHEDULE_1, SCHEDULE_1_COPY, SCHEDULE_2, SCHEDULE_2_COPY);
+
+ ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
+ mDvrHistoryRowAdapter =
+ new DvrHistoryRowAdapter(RuntimeEnvironment.application, presenterSelector, clock,
+ mDvrDataManager, new DefaultUiFlags());
+ }
+
+ @Test
+ public void testStart() {
+ mDvrHistoryRowAdapter.start();
+ assertInitialState();
+ }
+
+ @Test
+ public void testOnScheduledRecordingAdded_existingHeader() {
+ mDvrHistoryRowAdapter.start();
+ ScheduledRecording toAdd =
+ buildScheduledRecordingForTest(
+ 6,
+ 1518249600000L, // 2/10/2018
+ 1518250600000L,
+ ScheduledRecording.STATE_RECORDING_FAILED);
+ mDvrHistoryRowAdapter.onScheduledRecordingAdded(toAdd);
+
+ // a schedule row is added
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(7);
+ assertThat(getHeaderItemCounts()).containsExactly(2, 3).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(5);
+ }
+
+ @Test
+ public void testOnScheduledRecordingAdded_newHeader_addOldest() {
+ mDvrHistoryRowAdapter.start();
+ ScheduledRecording toAdd =
+ buildScheduledRecordingForTest(
+ 6,
+ 1518159600000L, // 2/8/2018 PST
+ 1518160600000L,
+ ScheduledRecording.STATE_RECORDING_FAILED);
+ mDvrHistoryRowAdapter.onScheduledRecordingAdded(toAdd);
+
+ // a header row and a schedule row are added
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(8);
+ assertThat(getHeaderItemCounts()).containsExactly(2, 2, 1).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(5);
+ }
+
+ @Test
+ public void testOnScheduledRecordingAdded_newHeader_addBetween() {
+ mDvrHistoryRowAdapter.start();
+ ScheduledRecording toAdd =
+ buildScheduledRecordingForTest(
+ 6,
+ 1518336000000L, // 2/11/2018 PST
+ 1518337000000L,
+ ScheduledRecording.STATE_RECORDING_FAILED);
+ mDvrHistoryRowAdapter.onScheduledRecordingAdded(toAdd);
+
+ // a header row and a schedule row are added
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(8);
+ assertThat(getHeaderItemCounts()).containsExactly(2, 1, 2).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(5);
+ }
+
+ @Test
+ public void testOnScheduledRecordingAdded_newHeader_addNewest() {
+ mDvrHistoryRowAdapter.start();
+ ScheduledRecording toAdd =
+ buildScheduledRecordingForTest(
+ 6,
+ 1518681600000L, // 2/15/2018 PST
+ 1518682600000L,
+ ScheduledRecording.STATE_RECORDING_FAILED);
+ mDvrHistoryRowAdapter.onScheduledRecordingAdded(toAdd);
+
+ // a header row and a schedule row are added
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(8);
+ assertThat(getHeaderItemCounts()).containsExactly(1, 2, 2).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(5);
+ }
+
+ @Test
+ public void testOnScheduledRecordingAdded_addRecordedProgram() {
+ mDvrHistoryRowAdapter.start();
+ mDvrHistoryRowAdapter.onScheduledRecordingAdded(sRecordedProgram);
+
+ // a header row and a schedule row are added
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(7);
+ assertThat(getHeaderItemCounts()).containsExactly(2, 3).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(5);
+ }
+
+ @Test
+ public void testOnScheduledRecordingRemoved_keepHeader() {
+ mDvrHistoryRowAdapter.start();
+ mDvrHistoryRowAdapter.onScheduledRecordingRemoved(SCHEDULE_1);
+
+ // a schedule row is removed
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(5);
+ assertThat(getHeaderItemCounts()).containsExactly(2, 1).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(3);
+ }
+
+ @Test
+ public void testOnScheduledRecordingRemoved_removeHeader() {
+ mDvrHistoryRowAdapter.start();
+ mDvrHistoryRowAdapter.onScheduledRecordingRemoved(SCHEDULE_1);
+ mDvrHistoryRowAdapter.onScheduledRecordingRemoved(SCHEDULE_1_COPY);
+
+ // a header row and a schedule row are removed
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(3);
+ assertThat(getHeaderItemCounts()).containsExactly(2).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(2);
+ }
+
+ @Test
+ public void testOnScheduledRecordingRemoved_removeRecordedProgram() {
+ mDvrDataManager.addRecordedProgramInternal(sRecordedProgram, true);
+ mDvrHistoryRowAdapter.start();
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(7);
+ assertThat(getHeaderItemCounts()).containsExactly(2, 3).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(5);
+
+ mDvrHistoryRowAdapter.onScheduledRecordingRemoved(sRecordedProgram);
+
+ // a schedule row is removed
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(6);
+ assertThat(getHeaderItemCounts()).containsExactly(2, 2);
+ assertThat(getScheduleSize()).isEqualTo(4);
+ }
+
+ private static ScheduledRecording buildScheduledRecordingForTest(
+ long id, long startTime, long endTime, int state) {
+ ScheduledRecording.Builder builder =
+ ScheduledRecording.builder("fakeInput", 1, startTime, endTime)
+ .setId(id)
+ .setState(state);
+ return builder.build();
+ }
+
+ private static RecordedProgram buildRecordedProgramForTest(
+ long id, long startTime, long endTime) {
+ RecordedProgram.Builder builder =
+ RecordedProgram.builder()
+ .setId(id)
+ .setInputId("fakeInput")
+ .setStartTimeUtcMillis(startTime)
+ .setEndTimeUtcMillis(endTime);
+ return builder.build();
+ }
+
+ private int getScheduleSize() {
+ int size = 0;
+ for (int i = 0; i < mDvrHistoryRowAdapter.size(); i++) {
+ Object item = mDvrHistoryRowAdapter.get(i);
+ if (item instanceof ScheduleRow && ((ScheduleRow) item).getSchedule() != null) {
+ size++;
+ }
+ }
+ return size;
+ }
+
+ private List<Integer> getHeaderItemCounts() {
+ List<Integer> result = new ArrayList<>();
+ for (int i = 0; i < mDvrHistoryRowAdapter.size(); i++) {
+ Object item = mDvrHistoryRowAdapter.get(i);
+ if (item instanceof SchedulesHeaderRow) {
+ int count = ((SchedulesHeaderRow) item).getItemCount();
+ assertThat(count).isAtLeast(1);
+ result.add(count);
+ }
+ }
+ return result;
+ }
+
+ private void assertInitialState() {
+ assertThat(mDvrHistoryRowAdapter.size()).isEqualTo(6);
+ assertThat(getHeaderItemCounts()).containsExactly(2, 2).inOrder();
+ assertThat(getScheduleSize()).isEqualTo(4);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/dvr/ui/playback/DvrPlayerTest.java b/tests/robotests/src/com/android/tv/dvr/ui/playback/DvrPlayerTest.java
new file mode 100644
index 0000000..28069b6
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/dvr/ui/playback/DvrPlayerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.dvr.ui.playback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.tv.TvTrackInfo;
+import com.android.tv.ShadowTvView;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.ui.AppLayerTvView;
+import java.util.ArrayList;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Test for {@link DvrPlayer}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ sdk = ConfigConstants.SDK,
+ application = TestSingletonApp.class,
+ shadows = {ShadowTvView.class})
+public class DvrPlayerTest {
+ private ShadowTvView mShadowTvView;
+ private DvrPlayer mDvrPlayer;
+
+ @Before
+ public void setUp() {
+ AppLayerTvView tvView = new AppLayerTvView(RuntimeEnvironment.application);
+ mShadowTvView = Shadow.extract(tvView);
+ mDvrPlayer = new DvrPlayer(tvView, RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void testGetAudioTracks_null() {
+ mShadowTvView.mTracks.put(TvTrackInfo.TYPE_AUDIO, null);
+ assertThat(mDvrPlayer.getAudioTracks()).isNotNull();
+ assertThat(mDvrPlayer.getAudioTracks()).isEmpty();
+ }
+
+ @Test
+ public void testGetAudioTracks_empty() {
+ mShadowTvView.mTracks.put(TvTrackInfo.TYPE_AUDIO, new ArrayList<>());
+ assertThat(mDvrPlayer.getAudioTracks()).isNotNull();
+ assertThat(mDvrPlayer.getAudioTracks()).isEmpty();
+ }
+
+ @Test
+ public void testGetAudioTracks_nonEmpty() {
+ TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "fake id").build();
+ mShadowTvView.mTracks.put(TvTrackInfo.TYPE_AUDIO, Collections.singletonList(info));
+ assertThat(mDvrPlayer.getAudioTracks()).containsExactly(info);
+ }
+
+}
diff --git a/tests/robotests/src/com/android/tv/guide/ProgramItemViewTest.java b/tests/robotests/src/com/android/tv/guide/ProgramItemViewTest.java
new file mode 100644
index 0000000..e7850c1
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/guide/ProgramItemViewTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2019 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.guide;
+
+import static com.google.android.libraries.testing.truth.TextViewSubject.assertThat;
+
+import android.support.annotation.NonNull;
+import android.view.LayoutInflater;
+
+import com.android.tv.R;
+import com.android.tv.common.util.Clock;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.guide.ProgramItemViewTest.TestApp;
+import com.android.tv.guide.ProgramItemViewTest.TestModule.Contributes;
+import com.android.tv.guide.ProgramManager.TableEntry;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.robo.RobotTestAppHelper;
+import com.android.tv.testing.testdata.TestData;
+
+import dagger.Component;
+import dagger.Module;
+import dagger.Provides;
+import dagger.android.AndroidInjectionModule;
+import dagger.android.AndroidInjector;
+import dagger.android.ContributesAndroidInjector;
+import dagger.android.DispatchingAndroidInjector;
+import dagger.android.HasAndroidInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+/** Tests for {@link ProgramItemView}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestApp.class)
+public class ProgramItemViewTest {
+
+ /** TestApp for {@link ProgramItemViewTest} */
+ public static class TestApp extends TestSingletonApp implements HasAndroidInjector {
+ @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ DaggerProgramItemViewTest_TestAppComponent.builder()
+ .testModule(new TestModule(this))
+ .build()
+ .inject(this);
+ }
+
+ @Override
+ public AndroidInjector<Object> androidInjector() {
+ return dispatchingAndroidInjector;
+ }
+ }
+
+ /** Component for {@link ProgramItemViewTest} */
+ @Component(
+ modules = {
+ AndroidInjectionModule.class,
+ TestModule.class,
+ })
+ interface TestAppComponent extends AndroidInjector<TestApp> {}
+
+ /** Module for {@link ProgramItemViewTest} */
+ @Module(includes = {Contributes.class})
+ public static class TestModule {
+
+ @Module()
+ public abstract static class Contributes {
+ @ContributesAndroidInjector
+ abstract ProgramItemView contributesProgramItemView();
+ }
+
+ private final TestApp myTestApp;
+
+ TestModule(TestApp test) {
+ myTestApp = test;
+ }
+
+ @Provides
+ ChannelDataManager providesChannelDataManager() {
+ return myTestApp.getChannelDataManager();
+ }
+
+ @Provides
+ Clock provideClock() {
+ return myTestApp.getClock();
+ }
+ }
+
+ // Thursday, June 1, 2017 1:00:00 PM GMT-07:00
+ private final long testStartTimeMs = 1496347200000L;
+
+ // Thursday, June 1, 2017 8:00:00 PM GMT-07:00
+ private final long eightPM = 1496372400000L;
+ private final ProgramImpl baseProgram =
+ new ProgramImpl.Builder()
+ .setChannelId(1)
+ .setStartTimeUtcMillis(eightPM)
+ .setEndTimeUtcMillis(eightPM + Duration.ofHours(1).toMillis())
+ .build();
+
+ private ProgramItemView mPprogramItemView;
+
+ @Mock DvrManager dvrManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ TestSingletonApp app = (TestSingletonApp) RuntimeEnvironment.application;
+ app.dvrManager = dvrManager;
+ app.fakeClock.setBootTimeMillis(testStartTimeMs + TimeUnit.HOURS.toMillis(-12));
+ app.fakeClock.setCurrentTimeMillis(testStartTimeMs);
+ RobotTestAppHelper.loadTestData(app, TestData.DEFAULT_10_CHANNELS);
+ mPprogramItemView =
+ (ProgramItemView)
+ LayoutInflater.from(RuntimeEnvironment.application)
+ .inflate(R.layout.program_guide_table_item, null);
+ GuideUtils.setWidthPerHour(100);
+ }
+
+ @Test
+ public void initialState() {
+ assertThat(mPprogramItemView).hasEmptyText();
+ }
+
+ @Test
+ public void setValue_noProgram() {
+ TableEntry tableEntry = create30MinuteTableEntryFor(null, null, false);
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("a gap");
+ assertThat(mPprogramItemView).hasContentDescription("1 a gap 8:00 – 9:00 PM");
+ }
+
+ @Test
+ public void setValue_programNoTitle() {
+ ProgramImpl program = new ProgramImpl.Builder(baseProgram).build();
+ TableEntry tableEntry = create30MinuteTableEntryFor(program, null, false);
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("No information");
+ assertThat(mPprogramItemView).hasContentDescription("1 No information 8:00 – 9:00 PM");
+ }
+
+ @Test
+ public void setValue_programTitle() {
+ ProgramImpl program =
+ new ProgramImpl.Builder(baseProgram).setTitle("A good program").build();
+ TableEntry tableEntry = create30MinuteTableEntryFor(program, null, false);
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("A good program");
+ assertThat(mPprogramItemView).hasContentDescription("1 A good program 8:00 – 9:00 PM");
+ }
+
+ @Test
+ public void setValue_programDescriptionBlocked() {
+ ProgramImpl program =
+ new ProgramImpl.Builder(baseProgram)
+ .setTitle("A good program")
+ .setDescription("Naughty")
+ .build();
+ TableEntry tableEntry = create30MinuteTableEntryFor(program, null, true);
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("A good program");
+ assertThat(mPprogramItemView)
+ .hasContentDescription("1 A good program 8:00 – 9:00 PM This program is blocked");
+ }
+
+ @Test
+ public void setValue_programEpisode() {
+ ProgramImpl program =
+ new ProgramImpl.Builder(baseProgram)
+ .setTitle("A good program")
+ .setEpisodeTitle("The one with an episode")
+ .build();
+ TableEntry tableEntry = create30MinuteTableEntryFor(program, null, false);
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("A good program\n\u200DThe one with an episode");
+ assertThat(mPprogramItemView)
+ .hasContentDescription("1 A good program 8:00 – 9:00 PM The one with an episode");
+ }
+
+ @Test
+ public void setValue_programEpisodeAndDescrition() {
+ ProgramImpl program =
+ new ProgramImpl.Builder(baseProgram)
+ .setTitle("A good program")
+ .setEpisodeTitle("The one with an episode")
+ .setDescription("Jack and Jill go up a hill")
+ .build();
+ TableEntry tableEntry = create30MinuteTableEntryFor(program, null, false);
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("A good program\n\u200DThe one with an episode");
+ assertThat(mPprogramItemView)
+ .hasContentDescription(
+ "1 A good program 8:00 – 9:00 PM The one with an episode"
+ + " Jack and Jill go up a hill");
+ }
+
+ @Test
+ public void setValue_scheduledConflict() {
+ ProgramImpl program =
+ new ProgramImpl.Builder(baseProgram).setTitle("A good program").build();
+ ScheduledRecording scheduledRecording =
+ ScheduledRecording.builder("input1", program).build();
+ TableEntry tableEntry = create30MinuteTableEntryFor(program, scheduledRecording, false);
+ Mockito.when(dvrManager.isConflicting(scheduledRecording)).thenReturn(true);
+
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("A good program");
+ assertThat(mPprogramItemView)
+ .hasContentDescription("1 A good program 8:00 – 9:00 PM Recording conflict");
+ }
+
+ @Test
+ public void setValue_scheduled() {
+ ProgramImpl program =
+ new ProgramImpl.Builder(baseProgram).setTitle("A good program").build();
+ ScheduledRecording scheduledRecording =
+ ScheduledRecording.builder("input1", program)
+ .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)
+ .build();
+ TableEntry tableEntry = create30MinuteTableEntryFor(program, scheduledRecording, false);
+ Mockito.when(dvrManager.isConflicting(scheduledRecording)).thenReturn(false);
+
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("A good program");
+ assertThat(mPprogramItemView)
+ .hasContentDescription("1 A good program 8:00 – 9:00 PM Recording scheduled");
+ }
+
+ @Test
+ public void setValue_recordingInProgress() {
+ ProgramImpl program =
+ new ProgramImpl.Builder(baseProgram).setTitle("A good program").build();
+ ScheduledRecording scheduledRecording =
+ ScheduledRecording.builder("input1", program)
+ .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS)
+ .build();
+ TableEntry tableEntry = create30MinuteTableEntryFor(program, scheduledRecording, false);
+ Mockito.when(dvrManager.isConflicting(scheduledRecording)).thenReturn(false);
+
+ mPprogramItemView.setValues(null, tableEntry, 0, 0, 0, "a gap");
+ assertThat(mPprogramItemView).hasText("A good program");
+ assertThat(mPprogramItemView)
+ .hasContentDescription("1 A good program 8:00 – 9:00 PM Recording");
+ }
+
+ @NonNull
+ private TableEntry create30MinuteTableEntryFor(
+ ProgramImpl program, ScheduledRecording scheduledRecording, boolean isBlocked) {
+ return ProgramManager.createTableEntryForTest(
+ 1,
+ program,
+ scheduledRecording,
+ eightPM,
+ eightPM + Duration.ofHours(1).toMillis(),
+ isBlocked);
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/guide/ProgramTableAdapterTest.java b/tests/robotests/src/com/android/tv/guide/ProgramTableAdapterTest.java
new file mode 100644
index 0000000..5207274
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/guide/ProgramTableAdapterTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 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.guide;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+
+import com.android.tv.common.flags.impl.DefaultUiFlags;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ChannelImpl;
+import com.android.tv.data.GenreItems;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link ProgramTableAdapter}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
+public class ProgramTableAdapterTest {
+
+ @Mock private ProgramGuide mProgramGuide;
+ @Mock private ChannelDataManager mChannelDataManager;
+ @Mock private ProgramDataManager mProgramDataManager;
+ private ProgramManager mProgramManager;
+
+ // Thursday, June 1, 2017 1:00:00 PM GMT-07:00
+ private final long mTestStartTimeMs = 1496347200000L;
+ // Thursday, June 1, 2017 8:00:00 PM GMT-07:00
+ private final long mEightPM = 1496372400000L;
+ private DefaultUiFlags mUiFlags;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ TestSingletonApp app = (TestSingletonApp) RuntimeEnvironment.application;
+ app.fakeClock.setBootTimeMillis(mTestStartTimeMs + TimeUnit.HOURS.toMillis(-12));
+ app.fakeClock.setCurrentTimeMillis(mTestStartTimeMs);
+ mUiFlags = new DefaultUiFlags();
+ mProgramManager =
+ new ProgramManager(
+ app.getTvInputManagerHelper(),
+ mChannelDataManager,
+ mProgramDataManager,
+ null,
+ null);
+ }
+
+ @Test
+ public void testOnTableEntryChanged() {
+ Mockito.when(mProgramGuide.getProgramManager()).thenReturn(mProgramManager);
+ Mockito.when(mProgramDataManager.getCurrentProgram(anyLong()))
+ .thenAnswer(
+ invocation -> {
+ long id = (long) invocation.getArguments()[0];
+ return buildProgramForTesting(
+ id, id, (int) id % GenreItems.getGenreCount());
+ });
+ ProgramTableAdapter programTableAdapter =
+ new ProgramTableAdapter(RuntimeEnvironment.application, mProgramGuide, mUiFlags);
+ mProgramManager.setChannels(buildChannelForTesting(1, 2, 3));
+ assertThat(mProgramManager.getChannelCount()).isEqualTo(3);
+
+ // set genre ID to 1. Then channel 1 is in the filtered list but channel 2 is not.
+ mProgramManager.resetChannelListWithGenre(1);
+ assertThat(mProgramManager.getChannelCount()).isEqualTo(1);
+ assertThat(mProgramManager.getChannelIndex(2)).isEqualTo(-1);
+
+ // should be no exception when onTableEntryChanged() is called
+ programTableAdapter.onTableEntryChanged(
+ ProgramManager.createTableEntryForTest(
+ 2,
+ mProgramDataManager.getCurrentProgram(2),
+ null,
+ mTestStartTimeMs,
+ mEightPM,
+ false));
+ }
+
+ private List<Channel> buildChannelForTesting(long... ids) {
+ List<Channel> channels = new ArrayList<>();
+ for (long id : ids) {
+ channels.add(new ChannelImpl.Builder().setId(id).build());
+ }
+ return channels;
+ }
+
+ private Program buildProgramForTesting(long id, long channelId, int genreId) {
+ return new ProgramImpl.Builder()
+ .setId(id)
+ .setChannelId(channelId)
+ .setCanonicalGenres(GenreItems.getCanonicalGenre(genreId))
+ .build();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/search/FakeSearchInterface.java b/tests/robotests/src/com/android/tv/search/FakeSearchInterface.java
new file mode 100644
index 0000000..568bddd
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/search/FakeSearchInterface.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.search;
+
+import android.content.Intent;
+import android.media.tv.TvContract;
+import android.media.tv.TvContract.Programs;
+
+import com.android.tv.data.api.Program;
+import com.android.tv.search.LocalSearchProvider.SearchResult;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Fake {@link SearchInterface} for testing. */
+public class FakeSearchInterface implements SearchInterface {
+ public final List<Program> mPrograms = new ArrayList<>();
+
+ @Override
+ public List<SearchResult> search(String query, int limit, int action) {
+
+ List<SearchResult> results = new ArrayList<>();
+ for (Program program : mPrograms) {
+ if (program.getTitle().contains(query) || program.getDescription().contains(query)) {
+ results.add(fromProgram(program));
+ }
+ }
+ return results;
+ }
+
+ public static SearchResult fromProgram(Program program) {
+ SearchResult.Builder result = SearchResult.builder();
+ result.setTitle(program.getTitle());
+ result.setDescription(
+ program.getStartTimeUtcMillis() + " - " + program.getEndTimeUtcMillis());
+ result.setImageUri(program.getPosterArtUri());
+ result.setIntentAction(Intent.ACTION_VIEW);
+ result.setIntentData(TvContract.buildChannelUri(program.getChannelId()).toString());
+ result.setIntentExtraData(TvContract.buildProgramUri(program.getId()).toString());
+ result.setContentType(Programs.CONTENT_ITEM_TYPE);
+ result.setIsLive(true);
+ result.setVideoWidth(program.getVideoWidth());
+ result.setVideoHeight(program.getVideoHeight());
+ result.setDuration(program.getDurationMillis());
+ result.setChannelId(program.getChannelId());
+ return result.build();
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/search/LocalSearchProviderTest.java b/tests/robotests/src/com/android/tv/search/LocalSearchProviderTest.java
new file mode 100644
index 0000000..caceb3f
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/search/LocalSearchProviderTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.search;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import android.app.SearchManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.net.Uri;
+
+import com.android.tv.common.flags.impl.SettableFlagsModule;
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Program;
+import com.android.tv.perf.PerformanceMonitor;
+import com.android.tv.perf.stub.StubPerformanceMonitor;
+import com.android.tv.search.LocalSearchProvider.SearchResult;
+import com.android.tv.search.LocalSearchProviderTest.TestAppComponent.TestAppModule;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.robo.ContentProviders;
+
+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.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** Unit test for {@link LocalSearchProvider}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = LocalSearchProviderTest.TestApp.class)
+public class LocalSearchProviderTest {
+
+ /** Test app for {@link LocalSearchProviderTest} */
+ public static class TestApp extends TestSingletonApp implements HasAndroidInjector {
+ @Inject DispatchingAndroidInjector<Object> mDispatchingAndroidProvider;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ DaggerLocalSearchProviderTest_TestAppComponent.builder()
+ .settableFlagsModule(flagsModule)
+ .build()
+ .inject(this);
+ }
+
+ @Override
+ public AndroidInjector<Object> androidInjector() {
+ return mDispatchingAndroidProvider;
+ }
+ }
+
+ @Component(
+ modules = {
+ AndroidInjectionModule.class,
+ SettableFlagsModule.class,
+ LocalSearchProvider.Module.class,
+ TestAppModule.class
+ })
+ @Singleton
+ interface TestAppComponent extends AndroidInjector<TestApp> {
+ @Module
+ abstract static class TestAppModule {
+ @Provides
+ @Singleton
+ static PerformanceMonitor providePerformanceMonitor() {
+ return new StubPerformanceMonitor();
+ }
+ }
+ }
+
+ private final Program mProgram1 =
+ new ProgramImpl.Builder()
+ .setTitle("Dummy program")
+ .setDescription("Dummy program season 2")
+ .setPosterArtUri("FakeUri")
+ .setStartTimeUtcMillis(1516674000000L)
+ .setEndTimeUtcMillis(1516677000000L)
+ .setChannelId(7)
+ .setVideoWidth(1080)
+ .setVideoHeight(960)
+ .build();
+
+ private final String mAuthority = "com.google.android.tv.search";
+ private final String mKeyword = "mKeyword";
+ private final Uri mBaseSearchUri =
+ Uri.parse(
+ "content://"
+ + mAuthority
+ + "/"
+ + SearchManager.SUGGEST_URI_PATH_QUERY
+ + "/"
+ + mKeyword);
+
+ private final Uri mWrongSearchUri =
+ Uri.parse("content://" + mAuthority + "/wrong_path/" + mKeyword);
+
+ private LocalSearchProvider mProvider;
+ private ContentResolver mContentResolver;
+
+ @Mock private SearchInterface mMockSearchInterface;
+ private final FakeSearchInterface mFakeSearchInterface = new FakeSearchInterface();
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mProvider = ContentProviders.register(LocalSearchProvider.class, mAuthority);
+ mContentResolver = RuntimeEnvironment.application.getContentResolver();
+ }
+
+ @Test
+ public void testQuery_normalUri() {
+ verifyQueryWithArguments(null, null);
+ verifyQueryWithArguments(1, null);
+ verifyQueryWithArguments(null, 1);
+ verifyQueryWithArguments(1, 1);
+ }
+
+ @Test
+ public void testQuery_invalidUri() {
+ try (Cursor c = mContentResolver.query(mWrongSearchUri, null, null, null, null)) {
+ fail("Query with invalid URI should fail.");
+ } catch (IllegalArgumentException e) {
+ // Success.
+ }
+ }
+
+ @Test
+ public void testQuery_invalidLimit() {
+ verifyQueryWithArguments(-1, null);
+ }
+
+ @Test
+ public void testQuery_invalidAction() {
+ verifyQueryWithArguments(null, SearchInterface.ACTION_TYPE_START - 1);
+ verifyQueryWithArguments(null, SearchInterface.ACTION_TYPE_END + 1);
+ }
+
+ private void verifyQueryWithArguments(Integer limit, Integer action) {
+ mProvider.setSearchInterface(mMockSearchInterface);
+ Uri uri = mBaseSearchUri;
+ if (limit != null || action != null) {
+ Uri.Builder builder = uri.buildUpon();
+ if (limit != null) {
+ builder.appendQueryParameter(
+ SearchManager.SUGGEST_PARAMETER_LIMIT, limit.toString());
+ }
+ if (action != null) {
+ builder.appendQueryParameter(
+ LocalSearchProvider.SUGGEST_PARAMETER_ACTION, action.toString());
+ }
+ uri = builder.build();
+ }
+ try (Cursor c = mContentResolver.query(uri, null, null, null, null)) {
+ // Do nothing.
+ }
+ int expectedLimit =
+ limit == null || limit <= 0 ? LocalSearchProvider.DEFAULT_SEARCH_LIMIT : limit;
+ int expectedAction =
+ (action == null
+ || action < SearchInterface.ACTION_TYPE_START
+ || action > SearchInterface.ACTION_TYPE_END)
+ ? LocalSearchProvider.DEFAULT_SEARCH_ACTION
+ : action;
+ verify(mMockSearchInterface).search(mKeyword, expectedLimit, expectedAction);
+ reset(mMockSearchInterface);
+ }
+
+ @Test
+ public void testGetType() {
+ assertThat(mProvider.getType(mBaseSearchUri)).isEqualTo(SearchManager.SUGGEST_MIME_TYPE);
+ }
+
+ @Test
+ public void query_empty() {
+ mProvider.setSearchInterface(mFakeSearchInterface);
+ Cursor cursor = mContentResolver.query(mBaseSearchUri, null, null, null, null);
+ assertThat(cursor.moveToNext()).isFalse();
+ }
+
+ @Test
+ public void query_program1() {
+ mProvider.setSearchInterface(mFakeSearchInterface);
+ mFakeSearchInterface.mPrograms.add(mProgram1);
+ Uri uri =
+ Uri.parse(
+ "content://"
+ + mAuthority
+ + "/"
+ + SearchManager.SUGGEST_URI_PATH_QUERY
+ + "/"
+ + "Dummy");
+ Cursor cursor = mContentResolver.query(uri, null, null, null, null);
+ assertThat(fromCursor(cursor)).containsExactly(FakeSearchInterface.fromProgram(mProgram1));
+ }
+
+ private List<SearchResult> fromCursor(Cursor cursor) {
+ List<SearchResult> results = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ SearchResult.Builder result = SearchResult.builder();
+ int i = 0;
+ result.setTitle(cursor.getString(i++));
+ result.setDescription(cursor.getString(i++));
+ result.setImageUri(cursor.getString(i++));
+ result.setIntentAction(cursor.getString(i++));
+ String intentData = cursor.getString(i++);
+ result.setIntentData(intentData);
+ result.setIntentExtraData(cursor.getString(i++));
+ result.setContentType(cursor.getString(i++));
+ result.setIsLive(cursor.getString(i++).equals("1"));
+ result.setVideoWidth(Integer.valueOf(cursor.getString(i++)));
+ result.setVideoHeight(Integer.valueOf(cursor.getString(i++)));
+ result.setDuration(Long.valueOf(cursor.getString(i++)));
+ result.setProgressPercentage(Integer.valueOf(cursor.getString(i)));
+ result.setChannelId(ContentUris.parseId(Uri.parse(intentData)));
+ results.add(result.build());
+ }
+ return results;
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/testing/TvRobolectricTestRunner.java b/tests/robotests/src/com/android/tv/testing/TvRobolectricTestRunner.java
new file mode 100644
index 0000000..1efbee1
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/testing/TvRobolectricTestRunner.java
@@ -0,0 +1,91 @@
+/*
+ * 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.testing;
+
+import java.util.List;
+import org.junit.runners.model.InitializationError;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.manifest.AndroidManifest;
+import org.robolectric.res.Fs;
+import org.robolectric.res.ResourcePath;
+
+/**
+ * Custom test runner TV. This is needed because the default behavior for robolectric is just to
+ * grab the resource directory in the target package. We want to override this to add several
+ * spanning different projects.
+ *
+ * <p><b>Note</b> copied from
+ * http://cs/android/packages/apps/Settings/tests/robotests/src/com/android/settings/testutils/SettingsRobolectricTestRunner.java
+ */
+public class TvRobolectricTestRunner extends RobolectricTestRunner {
+
+ /** We don't actually want to change this behavior, so we just call super. */
+ public TvRobolectricTestRunner(Class<?> testClass) throws InitializationError {
+ super(testClass);
+ }
+
+ /**
+ * We are going to create our own custom manifest so that we can add multiple resource paths to
+ * it. This lets us access resources in both Settings and SettingsLib in our tests.
+ */
+ @Override
+ protected AndroidManifest getAppManifest(Config config) {
+ // Using the manifest file's relative path, we can figure out the application directory.
+ final String appRoot = "vendor/unbundled_google/packages/TV";
+ final String manifestPath = appRoot + "/AndroidManifest.xml";
+ final String resDir = appRoot + "/tests/robotests/res";
+ final String assetsDir = appRoot + config.assetDir();
+
+ // By adding any resources from libraries we need the AndroidManifest, we can access
+ // them from within the parallel universe's resource loader.
+ final AndroidManifest manifest =
+ new AndroidManifest(
+ Fs.fileFromPath(manifestPath),
+ Fs.fileFromPath(resDir),
+ Fs.fileFromPath(assetsDir)) {
+ @Override
+ public List<ResourcePath> getIncludedResourcePaths() {
+ List<ResourcePath> paths = super.getIncludedResourcePaths();
+ TvRobolectricTestRunner.getIncludedResourcePaths(getPackageName(), paths);
+ return paths;
+ }
+ };
+
+ // Set the package name to the renamed one
+ manifest.setPackageName("com.android.tv");
+ return manifest;
+ }
+
+ public static void getIncludedResourcePaths(String packageName, List<ResourcePath> paths) {
+ paths.add(
+ new ResourcePath(
+ packageName,
+ Fs.fileFromPath("./vendor/unbundled_google/packages/TV/res"),
+ null));
+ paths.add(
+ new ResourcePath(
+ packageName,
+ Fs.fileFromPath("./vendor/unbundled_google/packages/TV/common/res"),
+ null));
+ paths.add(
+ new ResourcePath(
+ packageName,
+ Fs.fileFromPath("./vendor/unbundled_google/packages/TV/tuner/res"),
+ null));
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/ui/ChannelBannerViewTest.java b/tests/robotests/src/com/android/tv/ui/ChannelBannerViewTest.java
new file mode 100644
index 0000000..bb645a8
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/ui/ChannelBannerViewTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 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.ui;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.tv.R;
+import com.android.tv.common.flags.impl.DefaultLegacyFlags;
+import com.android.tv.common.singletons.HasSingletons;
+import com.android.tv.data.api.Channel;
+import com.android.tv.data.api.Program;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.ui.ChannelBannerView.MySingletons;
+import com.android.tv.util.TvInputManagerHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import javax.inject.Provider;
+
+/** Tests for {@link ChannelBannerView}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class ChannelBannerViewTest {
+
+ private TestActivity testActivity;
+ private ChannelBannerView mChannelBannerView;
+ private ImageView mChannelSignalStrengthView;
+
+ @Before
+ public void setUp() {
+ testActivity = Robolectric.buildActivity(TestActivity.class).create().get();
+ mChannelBannerView =
+ (ChannelBannerView)
+ LayoutInflater.from(testActivity).inflate(R.layout.channel_banner, null);
+ mChannelSignalStrengthView = mChannelBannerView.findViewById(R.id.channel_signal_strength);
+ }
+
+ @Test
+ public void updateChannelSignalStrengthView_valueIsNotValid() {
+ mChannelBannerView.updateChannelSignalStrengthView(-1);
+ assertThat(mChannelSignalStrengthView.getVisibility()).isEqualTo(View.GONE);
+ mChannelBannerView.updateChannelSignalStrengthView(101);
+ assertThat(mChannelSignalStrengthView.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateChannelSignalStrengthView_20() {
+ mChannelBannerView.updateChannelSignalStrengthView(20);
+ assertThat(mChannelSignalStrengthView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(shadowOf(mChannelSignalStrengthView.getDrawable()).getCreatedFromResId())
+ .isEqualTo(R.drawable.quantum_ic_signal_cellular_0_bar_white_24);
+ }
+
+ @Test
+ public void updateChannelSignalStrengthView_40() {
+ mChannelBannerView.updateChannelSignalStrengthView(40);
+ assertThat(mChannelSignalStrengthView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(shadowOf(mChannelSignalStrengthView.getDrawable()).getCreatedFromResId())
+ .isEqualTo(R.drawable.quantum_ic_signal_cellular_1_bar_white_24);
+ }
+
+ @Test
+ public void updateChannelSignalStrengthView_60() {
+ mChannelBannerView.updateChannelSignalStrengthView(60);
+ assertThat(mChannelSignalStrengthView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(shadowOf(mChannelSignalStrengthView.getDrawable()).getCreatedFromResId())
+ .isEqualTo(R.drawable.quantum_ic_signal_cellular_2_bar_white_24);
+ }
+
+ @Test
+ public void updateChannelSignalStrengthView_80() {
+ mChannelBannerView.updateChannelSignalStrengthView(80);
+ assertThat(mChannelSignalStrengthView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(shadowOf(mChannelSignalStrengthView.getDrawable()).getCreatedFromResId())
+ .isEqualTo(R.drawable.quantum_ic_signal_cellular_3_bar_white_24);
+ }
+
+ @Test
+ public void updateChannelSignalStrengthView_100() {
+ mChannelBannerView.updateChannelSignalStrengthView(100);
+ assertThat(mChannelSignalStrengthView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(shadowOf(mChannelSignalStrengthView.getDrawable()).getCreatedFromResId())
+ .isEqualTo(R.drawable.quantum_ic_signal_cellular_4_bar_white_24);
+ }
+
+ /** An activity for {@link ChannelBannerViewTest}. */
+ public static class TestActivity extends Activity implements HasSingletons<MySingletons> {
+
+ MySingletonsImpl mSingletons = new MySingletonsImpl();
+ Context mContext = this;
+
+ @Override
+ public ChannelBannerView.MySingletons singletons() {
+ return mSingletons;
+ }
+
+ /** MySingletons implementation needed for this class. */
+ public class MySingletonsImpl implements ChannelBannerView.MySingletons {
+
+ @Override
+ public Provider<Channel> getCurrentChannelProvider() {
+ return null;
+ }
+
+ @Override
+ public Provider<Program> getCurrentProgramProvider() {
+ return null;
+ }
+
+ @Override
+ public Provider<TvOverlayManager> getOverlayManagerProvider() {
+ return null;
+ }
+
+ @Override
+ public TvInputManagerHelper getTvInputManagerHelperSingleton() {
+ return new TvInputManagerHelper(mContext, DefaultLegacyFlags.DEFAULT);
+ }
+
+ @Override
+ public Provider<Long> getCurrentPlayingPositionProvider() {
+ return null;
+ }
+
+ @Override
+ public DvrManager getDvrManagerSingleton() {
+ return null;
+ }
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/ui/hideable/AutoHideSchedulerTest.java b/tests/robotests/src/com/android/tv/ui/hideable/AutoHideSchedulerTest.java
new file mode 100644
index 0000000..6e30d72
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/ui/hideable/AutoHideSchedulerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 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.ui.hideable;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.testing.constants.ConfigConstants;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+/** Test for {@link AutoHideScheduler}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class AutoHideSchedulerTest {
+
+ private TestRunnable mTestRunnable = new TestRunnable();
+ private AutoHideScheduler mAutoHideScheduler;
+ private ShadowLooper mShadowLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ mShadowLooper = ShadowLooper.getShadowMainLooper();
+ mAutoHideScheduler = new AutoHideScheduler(RuntimeEnvironment.application, mTestRunnable);
+ }
+
+ @Test
+ public void initialState() {
+ assertThat(mAutoHideScheduler.isScheduled()).isFalse();
+ }
+
+ @Test
+ public void cancel() {
+ mAutoHideScheduler.cancel();
+ assertThat(mAutoHideScheduler.isScheduled()).isFalse();
+ }
+
+ @Test
+ public void schedule() {
+ mAutoHideScheduler.schedule(10);
+ assertThat(mAutoHideScheduler.isScheduled()).isTrue();
+ assertThat(mTestRunnable.runCount).isEqualTo(0);
+ }
+
+ @Test
+ public void setA11yEnabledThenSchedule() {
+ mAutoHideScheduler.onAccessibilityStateChanged(true);
+ mAutoHideScheduler.schedule(10);
+ assertThat(mAutoHideScheduler.isScheduled()).isFalse();
+ assertThat(mTestRunnable.runCount).isEqualTo(0);
+ }
+
+ @Test
+ public void scheduleThenCancel() {
+ mAutoHideScheduler.schedule(10);
+ assertThat(mAutoHideScheduler.isScheduled()).isTrue();
+ mAutoHideScheduler.cancel();
+ assertThat(mAutoHideScheduler.isScheduled()).isFalse();
+ assertThat(mTestRunnable.runCount).isEqualTo(0);
+ }
+
+ @Test
+ public void scheduleThenLoop() {
+ mAutoHideScheduler.schedule(10);
+ assertThat(mAutoHideScheduler.isScheduled()).isTrue();
+ assertThat(mTestRunnable.runCount).isEqualTo(0);
+ mShadowLooper.idle(9, TimeUnit.MILLISECONDS);
+ assertThat(mAutoHideScheduler.isScheduled()).isTrue();
+ assertThat(mTestRunnable.runCount).isEqualTo(0);
+ mShadowLooper.idle(1, TimeUnit.MILLISECONDS);
+ assertThat(mAutoHideScheduler.isScheduled()).isFalse();
+ assertThat(mTestRunnable.runCount).isEqualTo(1);
+ }
+
+ @Test
+ public void scheduleSetA11yEnabledThenLoop() {
+ mAutoHideScheduler.schedule(10);
+ assertThat(mAutoHideScheduler.isScheduled()).isTrue();
+ assertThat(mTestRunnable.runCount).isEqualTo(0);
+ mShadowLooper.idle(9, TimeUnit.MILLISECONDS);
+ assertThat(mAutoHideScheduler.isScheduled()).isTrue();
+ assertThat(mTestRunnable.runCount).isEqualTo(0);
+ mAutoHideScheduler.onAccessibilityStateChanged(true);
+ mShadowLooper.idle(1, TimeUnit.MILLISECONDS);
+ assertThat(mAutoHideScheduler.isScheduled()).isFalse();
+ assertThat(mTestRunnable.runCount).isEqualTo(0);
+ }
+
+ private static class TestRunnable implements Runnable {
+ int runCount = 0;
+
+ @Override
+ public void run() {
+ runCount++;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/util/MultiLongSparseArrayTest.java b/tests/robotests/src/com/android/tv/util/MultiLongSparseArrayTest.java
new file mode 100644
index 0000000..8c51e49
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/util/MultiLongSparseArrayTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.testing.constants.ConfigConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link MultiLongSparseArray}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class MultiLongSparseArrayTest {
+ @Test
+ public void testEmpty() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ assertThat(sparseArray.get(0)).isEmpty();
+ }
+
+ @Test
+ public void testOneElement() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.put(0, "foo");
+ assertThat(sparseArray.get(0)).containsExactly("foo");
+ }
+
+ @Test
+ public void testTwoElements() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.put(0, "foo");
+ sparseArray.put(0, "bar");
+ assertThat(sparseArray.get(0)).containsExactly("foo", "bar");
+ }
+
+ @Test
+ public void testClearEmptyCache() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.clearEmptyCache();
+ assertThat(sparseArray.getEmptyCacheSize()).isEqualTo(0);
+ sparseArray.put(0, "foo");
+ sparseArray.remove(0, "foo");
+ assertThat(sparseArray.getEmptyCacheSize()).isEqualTo(1);
+ sparseArray.clearEmptyCache();
+ assertThat(sparseArray.getEmptyCacheSize()).isEqualTo(0);
+ }
+
+ @Test
+ public void testMaxEmptyCacheSize() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.clearEmptyCache();
+ assertThat(sparseArray.getEmptyCacheSize()).isEqualTo(0);
+ for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) {
+ sparseArray.put(i, "foo");
+ }
+ for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) {
+ sparseArray.remove(i, "foo");
+ }
+ assertThat(sparseArray.getEmptyCacheSize())
+ .isEqualTo(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT);
+ sparseArray.clearEmptyCache();
+ assertThat(sparseArray.getEmptyCacheSize()).isEqualTo(0);
+ }
+
+ @Test
+ public void testReuseEmptySets() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.clearEmptyCache();
+ assertThat(sparseArray.getEmptyCacheSize()).isEqualTo(0);
+ // create a bunch of sets
+ for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) {
+ sparseArray.put(i, "foo");
+ }
+ // remove them so they are all put in the cache.
+ for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) {
+ sparseArray.remove(i, "foo");
+ }
+ assertThat(sparseArray.getEmptyCacheSize())
+ .isEqualTo(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT);
+
+ // now create elements, that use the cached empty sets.
+ for (int i = 0; i < MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT; i++) {
+ sparseArray.put(10 + i, "bar");
+ assertThat(sparseArray.getEmptyCacheSize())
+ .isEqualTo(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT - i - 1);
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/util/TvInputManagerHelperRoboTest.java b/tests/robotests/src/com/android/tv/util/TvInputManagerHelperRoboTest.java
new file mode 100644
index 0000000..837a18b
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/util/TvInputManagerHelperRoboTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import com.android.tv.common.flags.impl.DefaultLegacyFlags;
+import com.android.tv.testing.constants.ConfigConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests for {@link TvInputManagerHelper}.
+ *
+ * <p>This test is named ...RoboTest because there is already a test named <code>
+ * TvInputManagerHelperTest</code>
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class TvInputManagerHelperRoboTest {
+
+ @Test
+ public void getInputState_null() {
+ TvInputInfo tvinputInfo = null;
+ TvInputManagerHelper tvInputManagerHelper =
+ new TvInputManagerHelper(
+ RuntimeEnvironment.application, DefaultLegacyFlags.DEFAULT);
+ assertThat(TvInputManager.INPUT_STATE_DISCONNECTED)
+ .isSameInstanceAs(tvInputManagerHelper.getInputState(tvinputInfo));
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/util/TvProviderUtilsTest.java b/tests/robotests/src/com/android/tv/util/TvProviderUtilsTest.java
new file mode 100644
index 0000000..65734a8
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/util/TvProviderUtilsTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.ProviderInfo;
+import android.media.tv.TvContract;
+import android.os.Bundle;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.fakes.FakeTvProvider;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentResolver;
+
+/** Tests for {@link TvProviderUtils}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ sdk = ConfigConstants.SDK,
+ shadows = {ShadowContentResolver.class})
+public class TvProviderUtilsTest {
+
+ @Before
+ public void setUp() {
+ ProviderInfo info = new ProviderInfo();
+ info.authority = TvContract.AUTHORITY;
+ FakeTvProvider provider =
+ Robolectric.buildContentProvider(FakeTvProviderForTesting.class).create(info).get();
+ provider.onCreate();
+ ShadowContentResolver.registerProviderInternal(TvContract.AUTHORITY, provider);
+ }
+
+ @Test
+ public void testAddExtraColumnsToProjection() {
+ String[] inputStrings = {"column_1", "column_2", "column_3"};
+ assertThat(
+ TvProviderUtils
+ .addExtraColumnsToProjection(
+ inputStrings,
+ TvProviderUtils.EXTRA_PROGRAM_COLUMN_STATE))
+ .asList()
+ .containsExactly(
+ "column_1",
+ "column_2",
+ "column_3",
+ TvProviderUtils.EXTRA_PROGRAM_COLUMN_STATE)
+ .inOrder();
+ }
+
+ @Test
+ public void testAddExtraColumnsToProjection_extraColumnExists() {
+ String[] inputStrings = {
+ "column_1",
+ "column_2",
+ TvProviderUtils.EXTRA_PROGRAM_COLUMN_SERIES_ID,
+ TvProviderUtils.EXTRA_PROGRAM_COLUMN_STATE,
+ "column_3"
+ };
+ assertThat(
+ TvProviderUtils
+ .addExtraColumnsToProjection(
+ inputStrings,
+ TvProviderUtils.EXTRA_PROGRAM_COLUMN_STATE))
+ .asList()
+ .containsExactly(
+ "column_1",
+ "column_2",
+ TvProviderUtils.EXTRA_PROGRAM_COLUMN_SERIES_ID,
+ TvProviderUtils.EXTRA_PROGRAM_COLUMN_STATE,
+ "column_3")
+ .inOrder();
+ }
+
+ @Test
+ public void testGetExistingColumns_noException() {
+ FakeTvProviderForTesting.mThrowException = false;
+ FakeTvProviderForTesting.mBundleResult = new Bundle();
+ FakeTvProviderForTesting.mBundleResult.putStringArray(
+ TvContract.EXTRA_EXISTING_COLUMN_NAMES, new String[] {"column 1", "column 2"});
+ Set<String> columns =
+ TvProviderUtils.getExistingColumns(
+ RuntimeEnvironment.application, TvContract.Programs.CONTENT_URI);
+ assertThat(columns).containsExactly("column 1", "column 2");
+ }
+
+ @Test
+ public void testGetExistingColumns_throwsException() {
+ FakeTvProviderForTesting.mThrowException = true;
+ FakeTvProviderForTesting.mBundleResult = new Bundle();
+ // should be no exception here
+ Set<String> columns =
+ TvProviderUtils.getExistingColumns(
+ RuntimeEnvironment.application, TvContract.Programs.CONTENT_URI);
+ assertThat(columns).isEmpty();
+ }
+
+ private static class FakeTvProviderForTesting extends FakeTvProvider {
+ private static Bundle mBundleResult;
+ private static boolean mThrowException;
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ if (mThrowException) {
+ throw new IllegalStateException();
+ }
+ return mBundleResult;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/util/TvTrackInfoUtilsTest.java b/tests/robotests/src/com/android/tv/util/TvTrackInfoUtilsTest.java
new file mode 100644
index 0000000..f5621de
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/util/TvTrackInfoUtilsTest.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import static com.android.tv.util.TvTrackInfoUtils.getBestTrackInfo;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertEquals;
+
+import android.media.tv.TvTrackInfo;
+import com.android.tv.testing.ComparatorTester;
+import com.android.tv.testing.constants.ConfigConstants;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link com.android.tv.util.TvTrackInfoUtils}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class TvTrackInfoUtilsTest {
+
+ /** Tests for {@link TvTrackInfoUtils#getBestTrackInfo}. */
+ private static final String UN_MATCHED_ID = "no matching ID";
+
+ private final TvTrackInfo info1En1 = create("1", "en", 1);
+
+ private final TvTrackInfo info2En5 = create("2", "en", 5);
+
+ private final TvTrackInfo info3Fr8 = create("3", "fr", 8);
+
+ private final TvTrackInfo info4Null2 = create("4", null, 2);
+
+ private final TvTrackInfo info5Null6 = create("5", null, 6);
+
+ private TvTrackInfo create(String id, String fr, int audioChannelCount) {
+ return new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, id)
+ .setLanguage(fr)
+ .setAudioChannelCount(audioChannelCount)
+ .build();
+ }
+
+ private final List<TvTrackInfo> all =
+ Arrays.asList(info1En1, info2En5, info3Fr8, info4Null2, info5Null6);
+ private final List<TvTrackInfo> nullLanguageTracks =
+ Arrays.asList(info4Null2, info5Null6);
+
+ @Test
+ public void testGetBestTrackInfo_empty() {
+ TvTrackInfo result = getBestTrackInfo(Collections.emptyList(), UN_MATCHED_ID, "en", 1);
+ assertWithMessage("best track ").that(result).isEqualTo(null);
+ }
+
+ @Test
+ public void testGetBestTrackInfo_exactMatch() {
+ TvTrackInfo result = getBestTrackInfo(all, "1", "en", 1);
+ assertWithMessage("best track ").that(result).isEqualTo(info1En1);
+ }
+
+ @Test
+ public void testGetBestTrackInfo_langAndChannelCountMatch() {
+ TvTrackInfo result = getBestTrackInfo(all, UN_MATCHED_ID, "en", 5);
+ assertWithMessage("best track ").that(result).isEqualTo(info2En5);
+ }
+
+ @Test
+ public void testGetBestTrackInfo_languageOnlyMatch() {
+ TvTrackInfo result = getBestTrackInfo(all, UN_MATCHED_ID, "fr", 1);
+ assertWithMessage("best track ").that(result).isEqualTo(info3Fr8);
+ }
+
+ @Test
+ public void testGetBestTrackInfo_channelCountOnlyMatchWithNullLanguage() {
+ TvTrackInfo result = getBestTrackInfo(all, UN_MATCHED_ID, null, 8);
+ assertWithMessage("best track ").that(result).isEqualTo(info3Fr8);
+ }
+
+ @Test
+ public void testGetBestTrackInfo_noMatches() {
+ TvTrackInfo result = getBestTrackInfo(all, UN_MATCHED_ID, "kr", 1);
+ assertWithMessage("best track ").that(result).isEqualTo(info1En1);
+ }
+
+ @Test
+ public void testGetBestTrackInfo_noMatchesWithNullLanguage() {
+ TvTrackInfo result = getBestTrackInfo(all, UN_MATCHED_ID, null, 0);
+ assertWithMessage("best track ").that(result).isEqualTo(info1En1);
+ }
+
+ @Test
+ public void testGetBestTrackInfo_channelCountAndIdMatch() {
+ TvTrackInfo result = getBestTrackInfo(nullLanguageTracks, "5", null, 6);
+ assertWithMessage("best track ").that(result).isEqualTo(info5Null6);
+ }
+
+ @Test
+ public void testComparator() {
+ Comparator<TvTrackInfo> comparator = TvTrackInfoUtils.createComparator("1", "en", 1);
+ ComparatorTester.withoutEqualsTest(comparator)
+ // lang not match
+ .addComparableGroup(
+ create("1", "kr", 1),
+ create("2", "kr", 2),
+ create("1", "ja", 1),
+ create("1", "ch", 1))
+ // lang match not count match
+ .addComparableGroup(
+ create("2", "en", 2), create("3", "en", 3), create("1", "en", 2))
+ // lang and count match
+ .addComparableGroup(create("2", "en", 1), create("3", "en", 1))
+ // all match
+ .addComparableGroup(create("1", "en", 1), create("1", "en", 1))
+ .test();
+ }
+
+ /** Tests for {@link TvTrackInfoUtils#needToShowSampleRate}. */
+
+ private final TvTrackInfo info6En1 = create("6", "en", 1);
+
+ private final TvTrackInfo info7En0 = create("7", "en", 0);
+
+ private final TvTrackInfo info8En0 = create("8", "en", 0);
+
+ private List<TvTrackInfo> trackList;
+
+ @Test
+ public void needToShowSampleRate_false() {
+ trackList = Arrays.asList(info1En1, info2En5);
+ assertEquals(
+ false,
+ TvTrackInfoUtils.needToShowSampleRate(
+ RuntimeEnvironment.application,
+ trackList));
+ }
+
+ @Test
+ public void needToShowSampleRate_sameLanguageAndChannelCount() {
+ trackList = Arrays.asList(info1En1, info6En1);
+ assertEquals(
+ true,
+ TvTrackInfoUtils.needToShowSampleRate(
+ RuntimeEnvironment.application,
+ trackList));
+ }
+
+ @Test
+ public void needToShowSampleRate_sameLanguageNoChannelCount() {
+ trackList = Arrays.asList(info7En0, info8En0);
+ assertEquals(
+ true,
+ TvTrackInfoUtils.needToShowSampleRate(
+ RuntimeEnvironment.application,
+ trackList));
+ }
+
+ /** Tests for {@link TvTrackInfoUtils#getMultiAudioString}. */
+ private static final String TRACK_ID = "test_track_id";
+ private static final int AUDIO_SAMPLE_RATE = 48000;
+
+ @Test
+ public void testAudioTrackLanguage() {
+ assertEquals(
+ "Korean",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("kor"),
+ false));
+ assertEquals(
+ "English",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng"),
+ false));
+ assertEquals(
+ "Unknown language",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo(null),
+ false));
+ assertEquals(
+ "Unknown language",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo(""),
+ false));
+ assertEquals(
+ "abc",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("abc"),
+ false));
+ }
+
+ @Test
+ public void testAudioTrackCount() {
+ assertEquals(
+ "English",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", -1),
+ false));
+ assertEquals(
+ "English",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 0),
+ false));
+ assertEquals(
+ "English (mono)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 1),
+ false));
+ assertEquals(
+ "English (stereo)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 2),
+ false));
+ assertEquals(
+ "English (3 channels)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 3),
+ false));
+ assertEquals(
+ "English (4 channels)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 4),
+ false));
+ assertEquals(
+ "English (5 channels)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 5),
+ false));
+ assertEquals(
+ "English (5.1 surround)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 6),
+ false));
+ assertEquals(
+ "English (7 channels)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 7),
+ false));
+ assertEquals(
+ "English (7.1 surround)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("eng", 8),
+ false));
+ }
+
+ @Test
+ public void testShowSampleRate() {
+ assertEquals(
+ "Korean (48kHz)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("kor", 0),
+ true));
+ assertEquals(
+ "Korean (7.1 surround, 48kHz)",
+ TvTrackInfoUtils.getMultiAudioString(
+ RuntimeEnvironment.application,
+ createAudioTrackInfo("kor", 8),
+ true));
+ }
+
+ private static TvTrackInfo createAudioTrackInfo(String language) {
+ return createAudioTrackInfo(language, 0);
+ }
+
+ private static TvTrackInfo createAudioTrackInfo(String language, int channelCount) {
+ return new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, TRACK_ID)
+ .setLanguage(language)
+ .setAudioChannelCount(channelCount)
+ .setAudioSampleRate(AUDIO_SAMPLE_RATE)
+ .build();
+ }
+
+ /** Tests for {@link TvTrackInfoUtils#toString */
+ @Test
+ public void toString_audioWithDetails() {
+ assertEquals(
+ "TvTrackInfo{type=Audio, id=1, language=en, "
+ + "description=test, audioChannelCount=1, audioSampleRate=5}",
+ TvTrackInfoUtils.toString(
+ new TvTrackInfo
+ .Builder(TvTrackInfo.TYPE_AUDIO, "1")
+ .setLanguage("en")
+ .setAudioChannelCount(1)
+ .setDescription("test")
+ .setAudioSampleRate(5)
+ .build()
+ ));
+ }
+
+ @Test
+ public void toString_audioWithDefaults() {
+ assertEquals(
+ "TvTrackInfo{type=Audio, id=2, language=null, "
+ + "description=null, audioChannelCount=0, audioSampleRate=0}",
+ TvTrackInfoUtils.toString(
+ new TvTrackInfo
+ .Builder(TvTrackInfo.TYPE_AUDIO, "2")
+ .build()
+ ));
+ }
+
+ @Test
+ public void toString_videoWithDetails() {
+ assertEquals(
+ "TvTrackInfo{type=Video, id=3, language=en, description=test, "
+ + "videoWidth=1, videoHeight=1, videoFrameRate=1.0, videoPixelAspectRatio=2.0}",
+ TvTrackInfoUtils.toString(
+ new TvTrackInfo
+ .Builder(TvTrackInfo.TYPE_VIDEO, "3")
+ .setLanguage("en")
+ .setDescription("test")
+ .setVideoWidth(1)
+ .setVideoHeight(1)
+ .setVideoFrameRate(1)
+ .setVideoPixelAspectRatio(2)
+ .build()
+ ));
+ }
+
+ @Test
+ public void toString_videoWithDefaults() {
+ assertEquals(
+ "TvTrackInfo{type=Video, id=4, language=null, description=null, "
+ + "videoWidth=0, videoHeight=0, videoFrameRate=0.0, videoPixelAspectRatio=1.0}",
+ TvTrackInfoUtils.toString(
+ new TvTrackInfo
+ .Builder(TvTrackInfo.TYPE_VIDEO, "4")
+ .build()
+ ));
+ }
+
+ @Test
+ public void toString_subtitleWithDetails() {
+ assertEquals(
+ "TvTrackInfo{type=Subtitle, id=5, language=en, description=test}",
+ TvTrackInfoUtils.toString(
+ new TvTrackInfo
+ .Builder(TvTrackInfo.TYPE_SUBTITLE, "5")
+ .setLanguage("en")
+ .setDescription("test")
+ .build()
+ ));
+ }
+
+ @Test
+ public void toString_subtitleWithDefaults() {
+ assertEquals(
+ "TvTrackInfo{type=Subtitle, id=6, language=null, description=null}",
+ TvTrackInfoUtils.toString(
+ new TvTrackInfo
+ .Builder(TvTrackInfo.TYPE_SUBTITLE, "6")
+ .build()
+ ));
+ }
+}
diff --git a/tests/robotests/src/com/android/tv/util/UtilsTest.java b/tests/robotests/src/com/android/tv/util/UtilsTest.java
new file mode 100644
index 0000000..4e8a858
--- /dev/null
+++ b/tests/robotests/src/com/android/tv/util/UtilsTest.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.text.format.DateUtils;
+import com.android.tv.testing.constants.ConfigConstants;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests for {@link com.android.tv.util.Utils#getDurationString}.
+ *
+ * <p>This test uses deprecated flags {@link DateUtils#FORMAT_12HOUR} and {@link
+ * DateUtils#FORMAT_24HOUR} to run this test independent to system's 12/24h format.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class UtilsTest {
+ // TODO: Mock Context so we can specify current time and locale for test.
+ private Locale mLocale;
+ private static final long DATE_THIS_YEAR_2_1_MS = getFebOfThisYearInMillis(1, 0, 0);
+
+ // All possible list for a parameter to test parameter independent result.
+ private static final boolean[] PARAM_USE_SHORT_FORMAT = {false, true};
+
+ @Before
+ public void setUp() {
+ // Set locale to US
+ mLocale = Locale.getDefault();
+ Locale.setDefault(Locale.US);
+ }
+
+ @After
+ public void tearDown() {
+ // Revive system locale.
+ Locale.setDefault(mLocale);
+ }
+
+ /** Return time in millis assuming that whose year is this year and month is Jan. */
+ private static long getJanOfThisYearInMillis(int date, int hour, int minutes) {
+ return new GregorianCalendar(getThisYear(), Calendar.JANUARY, date, hour, minutes)
+ .getTimeInMillis();
+ }
+
+ private static long getJanOfThisYearInMillis(int date, int hour) {
+ return getJanOfThisYearInMillis(date, hour, 0);
+ }
+
+ /** Return time in millis assuming that whose year is this year and month is Feb. */
+ private static long getFebOfThisYearInMillis(int date, int hour, int minutes) {
+ return new GregorianCalendar(getThisYear(), Calendar.FEBRUARY, date, hour, minutes)
+ .getTimeInMillis();
+ }
+
+ private static long getFebOfThisYearInMillis(int date, int hour) {
+ return getFebOfThisYearInMillis(date, hour, 0);
+ }
+
+ private static int getThisYear() {
+ return new GregorianCalendar().get(GregorianCalendar.YEAR);
+ }
+
+ @Test
+ public void testSameDateAndTime() {
+ assertEquals(
+ "3:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 3),
+ getFebOfThisYearInMillis(1, 3),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "03:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 3),
+ getFebOfThisYearInMillis(1, 3),
+ false,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ @Test
+ public void testDurationWithinToday() {
+ assertEquals(
+ "12:00 – 3:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 3),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "00:00 – 03:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 3),
+ false,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ @Test
+ public void testDurationFromYesterdayToToday() {
+ assertEquals(
+ "Jan 31, 3:00 AM – Feb 1, 4:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getJanOfThisYearInMillis(31, 3),
+ getFebOfThisYearInMillis(1, 4),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "Jan 31, 03:00 – Feb 1, 04:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getJanOfThisYearInMillis(31, 3),
+ getFebOfThisYearInMillis(1, 4),
+ false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals(
+ "1/31, 11:30 PM – 12:30 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getJanOfThisYearInMillis(31, 23, 30),
+ getFebOfThisYearInMillis(1, 0, 30),
+ true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "1/31, 23:30 – 00:30",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getJanOfThisYearInMillis(31, 23, 30),
+ getFebOfThisYearInMillis(1, 0, 30),
+ true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ @Test
+ public void testDurationFromTodayToTomorrow() {
+ assertEquals(
+ "Feb 1, 3:00 AM – Feb 2, 4:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 3),
+ getFebOfThisYearInMillis(2, 4),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "Feb 1, 03:00 – Feb 2, 04:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 3),
+ getFebOfThisYearInMillis(2, 4),
+ false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals(
+ "2/1, 3:00 AM – 2/2, 4:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 3),
+ getFebOfThisYearInMillis(2, 4),
+ true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "2/1, 03:00 – 2/2, 04:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 3),
+ getFebOfThisYearInMillis(2, 4),
+ true,
+ DateUtils.FORMAT_24HOUR));
+
+ assertEquals(
+ "Feb 1, 11:30 PM – Feb 2, 12:30 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 23, 30),
+ getFebOfThisYearInMillis(2, 0, 30),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "Feb 1, 23:30 – Feb 2, 00:30",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 23, 30),
+ getFebOfThisYearInMillis(2, 0, 30),
+ false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals(
+ "11:30 PM – 12:30 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 23, 30),
+ getFebOfThisYearInMillis(2, 0, 30),
+ true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "23:30 – 00:30",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 23, 30),
+ getFebOfThisYearInMillis(2, 0, 30),
+ true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ @Test
+ public void testDurationWithinTomorrow() {
+ assertEquals(
+ "Feb 2, 2:00 – 4:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 2),
+ getFebOfThisYearInMillis(2, 4),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "Feb 2, 02:00 – 04:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 2),
+ getFebOfThisYearInMillis(2, 4),
+ false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals(
+ "2/2, 2:00 – 4:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 2),
+ getFebOfThisYearInMillis(2, 4),
+ true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "2/2, 02:00 – 04:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 2),
+ getFebOfThisYearInMillis(2, 4),
+ true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ @Test
+ public void testStartOfDay() {
+ assertEquals(
+ "12:00 – 1:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 1),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "00:00 – 01:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 1),
+ false,
+ DateUtils.FORMAT_24HOUR));
+
+ assertEquals(
+ "Feb 2, 12:00 – 1:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 0),
+ getFebOfThisYearInMillis(2, 1),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "Feb 2, 00:00 – 01:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 0),
+ getFebOfThisYearInMillis(2, 1),
+ false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals(
+ "2/2, 12:00 – 1:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 0),
+ getFebOfThisYearInMillis(2, 1),
+ true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "2/2, 00:00 – 01:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 0),
+ getFebOfThisYearInMillis(2, 1),
+ true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ @Test
+ public void testEndOfDay() {
+ for (boolean useShortFormat : PARAM_USE_SHORT_FORMAT) {
+ assertEquals(
+ "11:00 PM – 12:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 23),
+ getFebOfThisYearInMillis(2, 0),
+ useShortFormat,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "23:00 – 00:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(1, 23),
+ getFebOfThisYearInMillis(2, 0),
+ useShortFormat,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ assertEquals(
+ "Feb 2, 11:00 PM – 12:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 23),
+ getFebOfThisYearInMillis(3, 0),
+ false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "Feb 2, 23:00 – 00:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 23),
+ getFebOfThisYearInMillis(3, 0),
+ false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals(
+ "2/2, 11:00 PM – 12:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 23),
+ getFebOfThisYearInMillis(3, 0),
+ true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "2/2, 23:00 – 00:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 23),
+ getFebOfThisYearInMillis(3, 0),
+ true,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals(
+ "2/2, 12:00 AM – 2/3, 12:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 0),
+ getFebOfThisYearInMillis(3, 0),
+ true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "2/2, 00:00 – 2/3, 00:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ getFebOfThisYearInMillis(2, 0),
+ getFebOfThisYearInMillis(3, 0),
+ true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ @Test
+ public void testMidnight() {
+ for (boolean useShortFormat : PARAM_USE_SHORT_FORMAT) {
+ assertEquals(
+ "12:00 AM",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ DATE_THIS_YEAR_2_1_MS,
+ DATE_THIS_YEAR_2_1_MS,
+ useShortFormat,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals(
+ "00:00",
+ Utils.getDurationString(
+ RuntimeEnvironment.application,
+ DATE_THIS_YEAR_2_1_MS,
+ DATE_THIS_YEAR_2_1_MS,
+ DATE_THIS_YEAR_2_1_MS,
+ useShortFormat,
+ DateUtils.FORMAT_24HOUR));
+ }
+ }
+
+ @Test
+ public void testIsInGivenDay() {
+ assertTrue(
+ Utils.isInGivenDay(
+ new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(),
+ new GregorianCalendar(2015, Calendar.JANUARY, 1, 0, 30).getTimeInMillis()));
+ }
+
+ @Test
+ public void testIsNotInGivenDay() {
+ assertFalse(
+ Utils.isInGivenDay(
+ new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(),
+ new GregorianCalendar(2015, Calendar.JANUARY, 2).getTimeInMillis()));
+ }
+
+ @Test
+ public void testIfTimeZoneApplied() {
+ TimeZone timeZone = TimeZone.getDefault();
+
+ TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
+
+ // 2015.01.01 00:00 in KST = 2014.12.31 15:00 in UTC
+ long date2015StartMs = new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis();
+
+ // 2015.01.01 10:00 in KST = 2015.01.01 01:00 in UTC
+ long date2015Start10AMMs =
+ new GregorianCalendar(2015, Calendar.JANUARY, 1, 10, 0).getTimeInMillis();
+
+ // Those two times aren't in the same day in UTC, but they are in KST.
+ assertTrue(Utils.isInGivenDay(date2015StartMs, date2015Start10AMMs));
+
+ TimeZone.setDefault(timeZone);
+ }
+
+ @Test
+ public void testIsInternalTvInputInvalidInternalInputId() {
+ String inputId = "tv.comp";
+ assertFalse(Utils.isInternalTvInput(RuntimeEnvironment.application, inputId));
+ }
+}
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index c7d2f52..5ee17de 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.tests" >
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23" />
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23" />
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java b/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java
index 4b85eaa..052123c 100644
--- a/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java
+++ b/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java
@@ -53,9 +53,9 @@
public void testOnSeekRequested() {
long seekToTimeMs = System.currentTimeMillis() - REQUEST_TIMEOUT_MS * 3;
mMediator.onSeekRequested(seekToTimeMs);
- assertWithMessage("Seek request time")
- .that(mMediator.mSeekRequestTimeMs)
- .isNotSameAs(INVALID_TIME);
+ assertWithMessage("Seek request time")
+ .that(mMediator.mSeekRequestTimeMs)
+ .isNotSameInstanceAs(INVALID_TIME);
assertWithMessage("Current position")
.that(mMediator.mCurrentPositionMs)
.isEqualTo(seekToTimeMs);
@@ -68,15 +68,15 @@
long newCurrentTimeMs = seekToTimeMs + REQUEST_TIMEOUT_MS;
mMediator.onSeekRequested(seekToTimeMs);
mMediator.onCurrentPositionChanged(newCurrentTimeMs);
- assertWithMessage("Seek request time")
- .that(mMediator.mSeekRequestTimeMs)
- .isNotSameAs(INVALID_TIME);
- assertWithMessage("Current position")
- .that(mMediator.mCurrentPositionMs)
- .isNotSameAs(seekToTimeMs);
- assertWithMessage("Current position")
- .that(mMediator.mCurrentPositionMs)
- .isNotSameAs(newCurrentTimeMs);
+ assertWithMessage("Seek request time")
+ .that(mMediator.mSeekRequestTimeMs)
+ .isNotSameInstanceAs(INVALID_TIME);
+ assertWithMessage("Current position")
+ .that(mMediator.mCurrentPositionMs)
+ .isNotSameInstanceAs(seekToTimeMs);
+ assertWithMessage("Current position")
+ .that(mMediator.mCurrentPositionMs)
+ .isNotSameInstanceAs(newCurrentTimeMs);
}
@UiThreadTest
diff --git a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java
index 71ccaf3..ecae18a 100644
--- a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java
+++ b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java
@@ -56,7 +56,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Matchers;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
/**
@@ -92,35 +92,29 @@
mContentResolver = new FakeContentResolver();
mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider);
mListener = new TestChannelDataManagerListener();
- getInstrumentation()
- .runOnMainSync(
- new Runnable() {
- @Override
- public void run() {
- TvInputManagerHelper mockHelper =
- Mockito.mock(TvInputManagerHelper.class);
- Mockito.when(mockHelper.hasTvInputInfo(Matchers.anyString()))
- .thenReturn(true);
- Context mockContext = Mockito.mock(Context.class);
- Mockito.when(mockContext.getContentResolver())
- .thenReturn(mContentResolver);
- Mockito.when(mockContext.checkSelfPermission(Matchers.anyString()))
- .thenAnswer(
- invocation -> {
- Object[] args = invocation.getArguments();
- return getTargetContext()
- .checkSelfPermission(((String) args[0]));
- });
-
- mChannelDataManager =
- new ChannelDataManager(
- mockContext,
- mockHelper,
- AsyncTask.SERIAL_EXECUTOR,
- mContentResolver);
- mChannelDataManager.addListener(mListener);
- }
+ getInstrumentation()
+ .runOnMainSync(
+ new Runnable() {
+ @Override
+ public void run() {
+ TvInputManagerHelper mockHelper = Mockito.mock(TvInputManagerHelper.class);
+ Mockito.when(mockHelper.hasTvInputInfo(ArgumentMatchers.anyString()))
+ .thenReturn(true);
+ Context mockContext = Mockito.mock(Context.class);
+ Mockito.when(mockContext.getContentResolver()).thenReturn(mContentResolver);
+ Mockito.when(mockContext.checkSelfPermission(ArgumentMatchers.anyString()))
+ .thenAnswer(
+ invocation -> {
+ Object[] args = invocation.getArguments();
+ return getTargetContext().checkSelfPermission(((String) args[0]));
});
+
+ mChannelDataManager =
+ new ChannelDataManager(
+ mockContext, mockHelper, AsyncTask.SERIAL_EXECUTOR, mContentResolver);
+ mChannelDataManager.addListener(mListener);
+ }
+ });
}
@After
diff --git a/tests/unit/src/com/android/tv/data/ChannelImplTest.java b/tests/unit/src/com/android/tv/data/ChannelImplTest.java
index 86cfab6..5879117 100644
--- a/tests/unit/src/com/android/tv/data/ChannelImplTest.java
+++ b/tests/unit/src/com/android/tv/data/ChannelImplTest.java
@@ -34,7 +34,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Matchers;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -46,7 +46,7 @@
// Used for testing TV inputs with invalid input package. This could happen when a TV input is
// uninstalled while drawing an app link card.
private static final String INVALID_TV_INPUT_PACKAGE_NAME = "com.android.tv.invalid_tv_input";
- // Used for testing TV inputs defined inside of Live TV.
+ // Used for testing TV inputs defined inside of TV app.
private static final String LIVE_CHANNELS_PACKAGE_NAME = "com.android.tv";
// Used for testing a TV input which doesn't have its leanback launcher activity.
private static final String NONE_LEANBACK_TV_INPUT_PACKAGE_NAME =
@@ -92,30 +92,26 @@
LEANBACK_TV_INPUT_PACKAGE_NAME))
.thenReturn(leanbackTvInputIntent);
- // Channel.getAppLinkIntent() calls initAppLinkTypeAndIntent() which calls
- // Intent.resolveActivityInfo() which calls PackageManager.getActivityInfo().
- Mockito.doAnswer(
- new Answer<ActivityInfo>() {
- @Override
- public ActivityInfo answer(InvocationOnMock invocation) {
- // We only check the package name, since the class name can be
- // changed
- // when an intent is changed to an uri and created from the uri.
- // (ex, ".className" -> "packageName.className")
- return mValidIntent
- .getComponent()
- .getPackageName()
- .equals(
- ((ComponentName)
- invocation
- .getArguments()[0])
- .getPackageName())
- ? TEST_ACTIVITY_INFO
- : null;
- }
- })
- .when(mockPackageManager)
- .getActivityInfo(Mockito.<ComponentName>any(), Mockito.anyInt());
+ // Channel.getAppLinkIntent() calls initAppLinkTypeAndIntent() which calls
+ // Intent.resolveActivityInfo() which calls PackageManager.getActivityInfo().
+ Mockito.doAnswer(
+ new Answer<ActivityInfo>() {
+ @Override
+ public ActivityInfo answer(InvocationOnMock invocation) {
+ // We only check the package name, since the class name can be
+ // changed
+ // when an intent is changed to an uri and created from the uri.
+ // (ex, ".className" -> "packageName.className")
+ return mValidIntent
+ .getComponent()
+ .getPackageName()
+ .equals(((ComponentName) invocation.getArguments()[0]).getPackageName())
+ ? TEST_ACTIVITY_INFO
+ : null;
+ }
+ })
+ .when(mockPackageManager)
+ .getActivityInfo(ArgumentMatchers.<ComponentName>any(), ArgumentMatchers.anyInt());
mMockContext = Mockito.mock(Context.class);
Mockito.when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
@@ -253,15 +249,15 @@
@Test
public void testComparator() {
TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class);
- Mockito.when(manager.isPartnerInput(Matchers.anyString()))
- .thenAnswer(
- new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- String inputId = (String) invocation.getArguments()[0];
- return PARTNER_INPUT_ID.equals(inputId);
- }
- });
+ Mockito.when(manager.isPartnerInput(ArgumentMatchers.anyString()))
+ .thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ String inputId = (String) invocation.getArguments()[0];
+ return PARTNER_INPUT_ID.equals(inputId);
+ }
+ });
Comparator<Channel> comparator = new TestChannelComparator(manager);
ComparatorTester<Channel> comparatorTester = ComparatorTester.withoutEqualsTest(comparator);
comparatorTester.addComparableGroup(
@@ -306,15 +302,15 @@
@Test
public void testComparatorLabel() {
TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class);
- Mockito.when(manager.isPartnerInput(Matchers.anyString()))
- .thenAnswer(
- new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- String inputId = (String) invocation.getArguments()[0];
- return PARTNER_INPUT_ID.equals(inputId);
- }
- });
+ Mockito.when(manager.isPartnerInput(ArgumentMatchers.anyString()))
+ .thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ String inputId = (String) invocation.getArguments()[0];
+ return PARTNER_INPUT_ID.equals(inputId);
+ }
+ });
Comparator<Channel> comparator = new ChannelComparatorWithDescriptionAsLabel(manager);
ComparatorTester<Channel> comparatorTester = ComparatorTester.withoutEqualsTest(comparator);
diff --git a/tests/unit/src/com/android/tv/features/FeaturesTest.java b/tests/unit/src/com/android/tv/features/FeaturesTest.java
deleted file mode 100644
index e35758c..0000000
--- a/tests/unit/src/com/android/tv/features/FeaturesTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 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.features;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Test for features. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FeaturesTest {
- @Test
- public void testPropertyFeatureKeyLength() {
- // This forces the class to be loaded and verifies all PropertyFeature key lengths.
- // If any keys are too long the test will fail to load.
- assertThat(TvFeatures.TEST_FEATURE.isEnabled(null)).isFalse();
- }
-}
diff --git a/tests/unit/src/com/android/tv/menu/MenuTest.java b/tests/unit/src/com/android/tv/menu/MenuTest.java
index e384c39..7058316 100644
--- a/tests/unit/src/com/android/tv/menu/MenuTest.java
+++ b/tests/unit/src/com/android/tv/menu/MenuTest.java
@@ -25,6 +25,7 @@
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
@@ -42,8 +43,10 @@
public void setUp() {
mMenuView = Mockito.mock(IMenuView.class);
MenuRowFactory factory = Mockito.mock(MenuRowFactory.class);
- Mockito.when(factory.createMenuRow(Mockito.any(Menu.class), Mockito.any(Class.class)))
- .thenReturn(null);
+ Mockito.when(
+ factory.createMenuRow(
+ ArgumentMatchers.any(Menu.class), ArgumentMatchers.any(Class.class)))
+ .thenReturn(null);
mVisibilityChangeListener = Mockito.mock(OnMenuVisibilityChangeListener.class);
mMenu = new Menu(getTargetContext(), mMenuView, factory, mVisibilityChangeListener);
mMenu.disableAnimationForTest();
diff --git a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java
index 5ecbdf0..64a055a 100644
--- a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java
+++ b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java
@@ -24,6 +24,7 @@
import android.text.TextUtils;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.tv.common.flags.impl.DefaultLegacyFlags;
import com.android.tv.testing.activities.BaseMainActivityTestCase;
import com.android.tv.testing.constants.Constants;
import com.android.tv.testing.testinput.ChannelState;
@@ -49,7 +50,9 @@
@Before
public void setUp() {
super.setUp();
- mTvOptionsRowAdapter = new TvOptionsRowAdapter(mActivity, Collections.emptyList());
+ mTvOptionsRowAdapter =
+ new TvOptionsRowAdapter(
+ mActivity, Collections.emptyList(), DefaultLegacyFlags.DEFAULT);
tuneToChannel(TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY);
waitUntilAudioTracksHaveSize(1);
waitUntilAudioTrackSelected(ChannelState.DEFAULT.getSelectedAudioTrackId());
@@ -73,10 +76,10 @@
waitUntilAudioTrackSelected(Constants.EN_STEREO_AUDIO_TRACK.getId());
boolean result = mTvOptionsRowAdapter.updateMultiAudioAction();
- assertWithMessage("update Action had change").that(result).isTrue();
- assertWithMessage("Multi Audio enabled")
- .that(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled())
- .isTrue();
+ assertWithMessage("update Action had change").that(result).isTrue();
+ assertWithMessage("Multi Audio enabled")
+ .that(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled())
+ .isTrue();
}
@Test
@@ -91,10 +94,10 @@
waitUntilAudioTrackSelected(Constants.GENERIC_AUDIO_TRACK.getId());
boolean result = mTvOptionsRowAdapter.updateMultiAudioAction();
- assertWithMessage("update Action had change").that(result).isTrue();
- assertWithMessage("Multi Audio enabled")
- .that(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled())
- .isFalse();
+ assertWithMessage("update Action had change").that(result).isTrue();
+ assertWithMessage("Multi Audio enabled")
+ .that(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled())
+ .isFalse();
}
@Test
@@ -110,10 +113,10 @@
waitUntilVideoTrackSelected(data.mSelectedVideoTrackId);
boolean result = mTvOptionsRowAdapter.updateMultiAudioAction();
- assertWithMessage("update Action had change").that(result).isTrue();
- assertWithMessage("Multi Audio enabled")
- .that(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled())
- .isFalse();
+ assertWithMessage("update Action had change").that(result).isTrue();
+ assertWithMessage("Multi Audio enabled")
+ .that(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled())
+ .isFalse();
}
private void waitUntilAudioTracksHaveSize(int expected) {
diff --git a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java
index b929a0a..eb012f4 100644
--- a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java
+++ b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java
@@ -26,7 +26,7 @@
import java.util.Random;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
-import org.mockito.Matchers;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -57,17 +57,16 @@
})
.when(dataManager)
.getChannelRecords();
- Mockito.doAnswer(
- new Answer<ChannelRecord>() {
- @Override
- public ChannelRecord answer(InvocationOnMock invocation)
- throws Throwable {
- long channelId = (long) invocation.getArguments()[0];
- return channelRecordSortedMap.get(channelId);
- }
- })
- .when(dataManager)
- .getChannelRecord(Matchers.anyLong());
+ Mockito.doAnswer(
+ new Answer<ChannelRecord>() {
+ @Override
+ public ChannelRecord answer(InvocationOnMock invocation) throws Throwable {
+ long channelId = (long) invocation.getArguments()[0];
+ return channelRecordSortedMap.get(channelId);
+ }
+ })
+ .when(dataManager)
+ .getChannelRecord(ArgumentMatchers.anyLong());
return dataManager;
}
@@ -131,7 +130,7 @@
// Time hopping with random minutes.
latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(mRandom.nextInt(30) + 1);
}
- long watchedDurationMs = mRandom.nextInt((int) maxWatchDurationMs) + 1;
+ long watchedDurationMs = mRandom.nextInt((int) maxWatchDurationMs) + 1L;
if (!addWatchLog(channelId, latestWatchEndTimeMs, watchedDurationMs)) {
return false;
}
diff --git a/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java
index d914905..6cb0e08 100644
--- a/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java
+++ b/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java
@@ -17,17 +17,22 @@
package com.android.tv.recommendation;
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.tv.data.Program;
+
+import com.android.tv.data.ProgramImpl;
+import com.android.tv.data.api.Program;
import com.android.tv.recommendation.RoutineWatchEvaluator.ProgramTime;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-import org.junit.runner.RunWith;
/** Tests for {@link RoutineWatchEvaluator}. */
@SmallTest
@@ -121,15 +126,15 @@
@Test
public void testCalculateTitleMatchScore_longerMatchIsBetter() {
String base = "foo bar baz";
- assertThat(
- new ScoredItem[] {
- score(base, ""),
- score(base, "bar"),
- score(base, "foo bar"),
- score(base, "foo bar baz")
- })
- .asList()
- .isOrdered();
+ assertThat(
+ new ScoredItem[] {
+ score(base, ""),
+ score(base, "bar"),
+ score(base, "foo bar"),
+ score(base, "foo bar baz")
+ })
+ .asList()
+ .isInOrder();
}
@Test
@@ -320,7 +325,7 @@
private Program createDummyProgram(Calendar startTime, long programDurationMs) {
long startTimeMs = startTime.getTimeInMillis();
- return new Program.Builder()
+ return new ProgramImpl.Builder()
.setStartTimeUtcMillis(startTimeMs)
.setEndTimeUtcMillis(startTimeMs + programDurationMs)
.build();
diff --git a/tests/unit/src/com/android/tv/util/MockTvSingletons.java b/tests/unit/src/com/android/tv/util/MockTvSingletons.java
index fd4b43c..d9ff5e7 100644
--- a/tests/unit/src/com/android/tv/util/MockTvSingletons.java
+++ b/tests/unit/src/com/android/tv/util/MockTvSingletons.java
@@ -17,16 +17,15 @@
package com.android.tv.util;
import android.content.Context;
+
import com.android.tv.InputSessionManager;
import com.android.tv.MainActivityWrapper;
import com.android.tv.TvApplication;
import com.android.tv.TvSingletons;
import com.android.tv.analytics.Analytics;
import com.android.tv.analytics.Tracker;
-import com.android.tv.common.experiments.ExperimentLoader;
import com.android.tv.common.flags.impl.DefaultBackendKnobsFlags;
import com.android.tv.common.flags.impl.DefaultCloudEpgFlags;
-import com.android.tv.common.flags.impl.DefaultConcurrentDvrPlaybackFlags;
import com.android.tv.common.flags.impl.DefaultUiFlags;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.singletons.HasSingletons;
@@ -34,7 +33,6 @@
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.ProgramDataManager;
-import com.android.tv.data.epg.EpgFetcher;
import com.android.tv.data.epg.EpgReader;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
@@ -42,11 +40,14 @@
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.recorder.RecordingScheduler;
import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.testing.FakeClock;
+import com.android.tv.testing.fakes.FakeClock;
import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
+
import com.google.common.base.Optional;
+
+import dagger.Lazy;
+
import java.util.concurrent.Executor;
-import javax.inject.Provider;
/** Mock {@link TvSingletons} class. */
public class MockTvSingletons implements TvSingletons, HasSingletons<TvSingletons> {
@@ -56,8 +57,6 @@
private final DefaultBackendKnobsFlags mBackendFlags = new DefaultBackendKnobsFlags();
private final DefaultCloudEpgFlags mCloudEpgFlags = new DefaultCloudEpgFlags();
private final DefaultUiFlags mUiFlags = new DefaultUiFlags();
- private final DefaultConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags =
- new DefaultConcurrentDvrPlaybackFlags();
private PerformanceMonitor mPerformanceMonitor;
public MockTvSingletons(Context context) {
@@ -78,21 +77,11 @@
}
@Override
- public boolean isChannelDataManagerLoadFinished() {
- return mApp.isChannelDataManagerLoadFinished();
- }
-
- @Override
public ProgramDataManager getProgramDataManager() {
return mApp.getProgramDataManager();
}
@Override
- public boolean isProgramDataManagerCurrentProgramsLoadFinished() {
- return mApp.isProgramDataManagerCurrentProgramsLoadFinished();
- }
-
- @Override
public PreviewDataManager getPreviewDataManager() {
return mApp.getPreviewDataManager();
}
@@ -148,16 +137,11 @@
}
@Override
- public Provider<EpgReader> providesEpgReader() {
+ public Lazy<EpgReader> providesEpgReader() {
return mApp.providesEpgReader();
}
@Override
- public EpgFetcher getEpgFetcher() {
- return mApp.getEpgFetcher();
- }
-
- @Override
public SetupUtils getSetupUtils() {
return mApp.getSetupUtils();
}
@@ -168,21 +152,11 @@
}
@Override
- public ExperimentLoader getExperimentLoader() {
- return mApp.getExperimentLoader();
- }
-
- @Override
public MainActivityWrapper getMainActivityWrapper() {
return mApp.getMainActivityWrapper();
}
@Override
- public com.android.tv.util.account.AccountHelper getAccountHelper() {
- return mApp.getAccountHelper();
- }
-
- @Override
public boolean isRunningInMainProcess() {
return mApp.isRunningInMainProcess();
}
@@ -222,11 +196,6 @@
}
@Override
- public DefaultConcurrentDvrPlaybackFlags getConcurrentDvrPlaybackFlags() {
- return mConcurrentDvrPlaybackFlags;
- }
-
- @Override
public TvSingletons singletons() {
return this;
}
diff --git a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java
index 7e35d76..befc425 100644
--- a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java
+++ b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java
@@ -29,6 +29,7 @@
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -210,48 +211,47 @@
private TvInputManagerHelper createMockTvInputManager() {
TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class);
- Mockito.doAnswer(
- new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- TvInputInfo info = (TvInputInfo) invocation.getArguments()[0];
- return TEST_INPUT_MAP.get(info.getId()).mIsPartnerInput;
- }
- })
- .when(manager)
- .isPartnerInput(Mockito.<TvInputInfo>any());
- Mockito.doAnswer(
- new Answer<String>() {
- @Override
- public String answer(InvocationOnMock invocation) throws Throwable {
- TvInputInfo info = (TvInputInfo) invocation.getArguments()[0];
- return TEST_INPUT_MAP.get(info.getId()).mLabel;
- }
- })
- .when(manager)
- .loadLabel(Mockito.<TvInputInfo>any());
- Mockito.doAnswer(
- new Answer<String>() {
- @Override
- public String answer(InvocationOnMock invocation) throws Throwable {
- TvInputInfo info = (TvInputInfo) invocation.getArguments()[0];
- return TEST_INPUT_MAP.get(info.getId()).mCustomLabel;
- }
- })
- .when(manager)
- .loadCustomLabel(Mockito.<TvInputInfo>any());
- Mockito.doAnswer(
- new Answer<TvInputInfo>() {
- @Override
- public TvInputInfo answer(InvocationOnMock invocation)
- throws Throwable {
- String inputId = (String) invocation.getArguments()[0];
- TvInputInfoWrapper inputWrapper = TEST_INPUT_MAP.get(inputId);
- return inputWrapper == null ? null : inputWrapper.mInput;
- }
- })
- .when(manager)
- .getTvInputInfo(Mockito.<String>any());
+ Mockito.doAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ TvInputInfo info = (TvInputInfo) invocation.getArguments()[0];
+ return TEST_INPUT_MAP.get(info.getId()).mIsPartnerInput;
+ }
+ })
+ .when(manager)
+ .isPartnerInput(ArgumentMatchers.<TvInputInfo>any());
+ Mockito.doAnswer(
+ new Answer<String>() {
+ @Override
+ public String answer(InvocationOnMock invocation) throws Throwable {
+ TvInputInfo info = (TvInputInfo) invocation.getArguments()[0];
+ return TEST_INPUT_MAP.get(info.getId()).mLabel;
+ }
+ })
+ .when(manager)
+ .loadLabel(ArgumentMatchers.<TvInputInfo>any());
+ Mockito.doAnswer(
+ new Answer<String>() {
+ @Override
+ public String answer(InvocationOnMock invocation) throws Throwable {
+ TvInputInfo info = (TvInputInfo) invocation.getArguments()[0];
+ return TEST_INPUT_MAP.get(info.getId()).mCustomLabel;
+ }
+ })
+ .when(manager)
+ .loadCustomLabel(ArgumentMatchers.<TvInputInfo>any());
+ Mockito.doAnswer(
+ new Answer<TvInputInfo>() {
+ @Override
+ public TvInputInfo answer(InvocationOnMock invocation) throws Throwable {
+ String inputId = (String) invocation.getArguments()[0];
+ TvInputInfoWrapper inputWrapper = TEST_INPUT_MAP.get(inputId);
+ return inputWrapper == null ? null : inputWrapper.mInput;
+ }
+ })
+ .when(manager)
+ .getTvInputInfo(ArgumentMatchers.<String>any());
return manager;
}
diff --git a/tests/unit/src/com/android/tv/util/images/ImageCacheTest.java b/tests/unit/src/com/android/tv/util/images/ImageCacheTest.java
index 4172213..6e96824 100644
--- a/tests/unit/src/com/android/tv/util/images/ImageCacheTest.java
+++ b/tests/unit/src/com/android/tv/util/images/ImageCacheTest.java
@@ -52,28 +52,28 @@
public void testPutIfLarger_smaller() throws Exception {
mImageCache.putIfNeeded(INFO_50);
- assertWithMessage("before").that(mImageCache.get(KEY)).isSameAs(INFO_50);
+ assertWithMessage("before").that(mImageCache.get(KEY)).isSameInstanceAs(INFO_50);
mImageCache.putIfNeeded(INFO_25);
- assertWithMessage("after").that(mImageCache.get(KEY)).isSameAs(INFO_50);
+ assertWithMessage("after").that(mImageCache.get(KEY)).isSameInstanceAs(INFO_50);
}
@Test
public void testPutIfLarger_larger() throws Exception {
mImageCache.putIfNeeded(INFO_50);
- assertWithMessage("before").that(mImageCache.get(KEY)).isSameAs(INFO_50);
+ assertWithMessage("before").that(mImageCache.get(KEY)).isSameInstanceAs(INFO_50);
mImageCache.putIfNeeded(INFO_100);
- assertWithMessage("after").that(mImageCache.get(KEY)).isSameAs(INFO_100);
+ assertWithMessage("after").that(mImageCache.get(KEY)).isSameInstanceAs(INFO_100);
}
@Test
public void testPutIfLarger_alreadyMax() throws Exception {
mImageCache.putIfNeeded(INFO_100);
- assertWithMessage("before").that(mImageCache.get(KEY)).isSameAs(INFO_100);
+ assertWithMessage("before").that(mImageCache.get(KEY)).isSameInstanceAs(INFO_100);
mImageCache.putIfNeeded(INFO_200);
- assertWithMessage("after").that(mImageCache.get(KEY)).isSameAs(INFO_100);
+ assertWithMessage("after").that(mImageCache.get(KEY)).isSameInstanceAs(INFO_100);
}
}
diff --git a/tuner/Android.bp b/tuner/Android.bp
index 215a1e5..d1df99f 100644
--- a/tuner/Android.bp
+++ b/tuner/Android.bp
@@ -20,25 +20,25 @@
sdk_version: "system_current",
resource_dirs: ["res"],
libs: [
- "tv-auto-value-jar",
- "tv-auto-factory-jar",
"android-support-annotations",
- "tv-error-prone-annotations-jar",
- "tv-guava-android-jar",
- "tv-javax-annotations-jar",
- "jsr330",
- "tv-lib-dagger",
- "tv-lib-exoplayer",
- "tv-lib-exoplayer-v2-core",
- "live-tv-tuner-proto",
"android-support-compat",
"android-support-core-ui",
"android-support-v7-palette",
"android-support-v7-recyclerview",
- "android-support-v17-leanback",
+ "androidx.leanback_leanback",
"androidx.tvprovider_tvprovider",
- "tv-lib-dagger-android",
+ "jsr330",
+ "live-tv-tuner-proto",
+ "tv-auto-value-jar",
+ "tv-auto-factory-jar",
"tv-common",
+ "tv-error-prone-annotations-jar",
+ "tv-guava-android-jar",
+ "tv-javax-annotations-jar",
+ "tv-lib-dagger",
+ "tv-lib-exoplayer",
+ "tv-lib-exoplayer-v2-core",
+ "tv-lib-dagger-android",
],
plugins: [
"tv-auto-value",
diff --git a/tuner/AndroidManifest.xml b/tuner/AndroidManifest.xml
index fd21771..c084f3f 100644
--- a/tuner/AndroidManifest.xml
+++ b/tuner/AndroidManifest.xml
@@ -18,7 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.tv.tuner"
android:versionCode="1">
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application tools:replace="android:appComponentFactory"
android:appComponentFactory="android.support.v4.app.CoreComponentFactory" />
</manifest>
diff --git a/tuner/SampleDvbTuner/AndroidManifest.xml b/tuner/SampleDvbTuner/AndroidManifest.xml
index 5ad927e..c46a8a6 100755
--- a/tuner/SampleDvbTuner/AndroidManifest.xml
+++ b/tuner/SampleDvbTuner/AndroidManifest.xml
@@ -19,7 +19,7 @@
<uses-sdk
android:minSdkVersion="23"
- android:targetSdkVersion="27" />
+ android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -29,6 +29,8 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+ <!-- Permission to modify Recorded Program -->
+ <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
<!-- Permissions/feature for USB tuner -->
<uses-permission android:name="android.permission.DVB_DEVICE" />
@@ -87,4 +89,4 @@
android:process="com.android.tv.tuner" />
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tuner/SampleDvbTuner/build.gradle b/tuner/SampleDvbTuner/build.gradle
index 657a425..28ad3e4 100644
--- a/tuner/SampleDvbTuner/build.gradle
+++ b/tuner/SampleDvbTuner/build.gradle
@@ -19,48 +19,35 @@
* Experimental gradle configuration. This file may not be up to date.
*/
-buildscript {
- repositories {
- mavenCentral()
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5'
- }
-}
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
-android {
- compileSdkVersion 26
- buildToolsVersion '28.0.2'
- dexOptions {
- preDexLibraries = false
- additionalParameters=['--core-library']
- javaMaxHeapSize "6g"
- }
- android {
- defaultConfig {
- resConfigs "en"
- }
- }
- defaultConfig {
- minSdkVersion 23
- targetSdkVersion 26
- versionCode 1
- versionName "1.0"
- }
- buildTypes {
- debug {
- minifyEnabled false
- }
- }
+android {
+ compileSdkVersion 28
+ buildToolsVersion '28.0.3'
+
compileOptions() {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
+
+ defaultConfig {
+ minSdkVersion 23
+ resConfigs "en"
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+
+ buildTypes {
+ debug {
+ minifyEnabled false
+ }
+ release {
+ minifyEnabled true
+ }
+ }
+
sourceSets {
main {
res.srcDirs = ['res']
@@ -70,22 +57,18 @@
}
}
-repositories {
- mavenCentral()
- jcenter()
- google()
-}
-
-final String SUPPORT_LIBS_VERSION = '26.1.0'
dependencies {
- implementation 'com.google.android.exoplayer:exoplayer-core:2.9.0'
- implementation 'com.google.android.exoplayer:exoplayer:r1.5.16'
- implementation "com.android.support:palette-v7:${SUPPORT_LIBS_VERSION}"
- implementation "com.android.support:leanback-v17:${SUPPORT_LIBS_VERSION}"
- implementation "com.android.support:support-tv-provider:${SUPPORT_LIBS_VERSION}"
- /*Not building with latest one (1.6.2)*/
- annotationProcessor 'com.google.auto.value:auto-value:1.5.4'
- implementation 'com.google.auto.value:auto-value:1.5.4'
- implementation project(':common')
- implementation project(':tuner')
+ implementation 'androidx.leanback:leanback:1.1.0-alpha02'
+ implementation 'androidx.palette:palette:1.0.0'
+ implementation 'androidx.tvprovider:tvprovider:1.0.0'
+
+ annotationProcessor 'com.google.auto.value:auto-value:1.5.3'
+ implementation 'com.google.auto.value:auto-value:1.5.3'
+ implementation 'com.google.dagger:dagger:2.23'
+ implementation 'com.google.dagger:dagger-android:2.23'
+ annotationProcessor 'com.google.dagger:dagger-android-processor:2.23'
+ annotationProcessor 'com.google.dagger:dagger-compiler:2.23'
+
+ implementation project(':common')
+ implementation project(':tuner')
}
diff --git a/tuner/SampleDvbTuner/robotests/javatests/com/android/tv/tuner/sample/dvb/util/SampleDvbConstantsTest.java b/tuner/SampleDvbTuner/robotests/javatests/com/android/tv/tuner/sample/dvb/util/SampleDvbConstantsTest.java
new file mode 100644
index 0000000..af2ce90
--- /dev/null
+++ b/tuner/SampleDvbTuner/robotests/javatests/com/android/tv/tuner/sample/dvb/util/SampleDvbConstantsTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.tuner.sample.dvb.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import androidx.test.core.app.ApplicationProvider;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.tuner.sample.dvb.tvinput.SampleDvbTunerTvInputService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link SampleDvbConstants}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class SampleDvbConstantsTest {
+
+ @Test
+ public void tunerInputId() {
+ assertThat(ComponentName.unflattenFromString(SampleDvbConstants.TUNER_INPUT_ID))
+ .isEqualTo(
+ new ComponentName(
+ ApplicationProvider.getApplicationContext(),
+ SampleDvbTunerTvInputService.class));
+ }
+}
diff --git a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/AndroidManifest.xml b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/AndroidManifest.xml
index dc04228..bf7c3f7 100644
--- a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/AndroidManifest.xml
+++ b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/AndroidManifest.xml
@@ -36,7 +36,7 @@
<uses-feature android:name="android.software.leanback" android:required="true" />
<uses-feature android:name="android.software.live_tv" android:required="true" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application
android:name=".app.SampleDvbTuner"
android:icon="@mipmap/ic_launcher"
diff --git a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTuner.java b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTuner.java
index 568e3c9..c45bb27 100644
--- a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTuner.java
+++ b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTuner.java
@@ -18,16 +18,19 @@
import android.content.ComponentName;
import android.media.tv.TvContract;
+
import com.android.tv.common.BaseApplication;
+import com.android.tv.common.dagger.ApplicationModule;
import com.android.tv.common.singletons.HasSingletons;
import com.android.tv.tuner.modules.TunerSingletonsModule;
import com.android.tv.tuner.sample.dvb.singletons.SampleDvbSingletons;
import com.android.tv.tuner.sample.dvb.tvinput.SampleDvbTunerTvInputService;
-import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
-import com.android.tv.tuner.tvinput.factory.TunerSessionFactoryImpl;
+
import dagger.android.AndroidInjector;
+
import com.android.tv.common.flags.CloudEpgFlags;
import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
import javax.inject.Inject;
/** The top level application for Sample DVB Tuner. */
@@ -37,7 +40,6 @@
private String mEmbeddedInputId;
@Inject CloudEpgFlags mCloudEpgFlags;
@Inject ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
- @Inject TunerSessionFactoryImpl mTunerSessionFactory;
@Override
public void onCreate() {
@@ -47,7 +49,7 @@
@Override
protected AndroidInjector<SampleDvbTuner> applicationInjector() {
return DaggerSampleDvbTunerComponent.builder()
- .sampleDvbTunerModule(new SampleDvbTunerModule(this))
+ .applicationModule(new ApplicationModule(this))
.tunerSingletonsModule(new TunerSingletonsModule(this))
.build();
}
@@ -73,16 +75,7 @@
}
@Override
- public ConcurrentDvrPlaybackFlags getConcurrentDvrPlaybackFlags() {
- return mConcurrentDvrPlaybackFlags;
- }
-
- @Override
public SampleDvbSingletons singletons() {
return this;
}
-
- public TunerSessionFactory getTunerSessionFactory() {
- return mTunerSessionFactory;
- }
}
diff --git a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTunerModule.java b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTunerModule.java
index 4da3ca9..aaaa101 100644
--- a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTunerModule.java
+++ b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTunerModule.java
@@ -15,38 +15,30 @@
*/
package com.android.tv.tuner.sample.dvb.app;
+import com.android.tv.common.dagger.ApplicationModule;
import com.android.tv.common.flags.impl.DefaultFlagsModule;
import com.android.tv.tuner.api.TunerFactory;
-import com.android.tv.tuner.builtin.BuiltInTunerHalFactory;
+import com.android.tv.tuner.dvb.DvbTunerHalFactory;
import com.android.tv.tuner.modules.TunerModule;
import com.android.tv.tuner.sample.dvb.setup.SampleDvbTunerSetupActivity;
import com.android.tv.tuner.sample.dvb.tvinput.SampleDvbTunerTvInputService;
-import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
+
import dagger.Module;
import dagger.Provides;
/** Dagger module for {@link SampleDvbTuner}. */
@Module(
includes = {
+ ApplicationModule.class,
DefaultFlagsModule.class,
SampleDvbTunerTvInputService.Module.class,
SampleDvbTunerSetupActivity.Module.class,
TunerModule.class,
})
class SampleDvbTunerModule {
- private final SampleDvbTuner mSampleDvbTuner;
-
- SampleDvbTunerModule(SampleDvbTuner sampleDvbTuner) {
- mSampleDvbTuner = sampleDvbTuner;
- }
-
- @Provides
- public TunerSessionFactory providesTunerSessionFactory() {
- return mSampleDvbTuner.getTunerSessionFactory();
- }
@Provides
TunerFactory providesTunerFactory() {
- return BuiltInTunerHalFactory.INSTANCE;
+ return DvbTunerHalFactory.INSTANCE;
}
}
diff --git a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/setup/SampleDvbTunerSetupActivity.java b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/setup/SampleDvbTunerSetupActivity.java
index f9ef29c..e2b5063 100644
--- a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/setup/SampleDvbTunerSetupActivity.java
+++ b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/setup/SampleDvbTunerSetupActivity.java
@@ -36,6 +36,7 @@
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.common.util.PostalCodeUtils;
import com.android.tv.tuner.sample.dvb.R;
+import com.android.tv.tuner.sample.dvb.util.SampleDvbConstants;
import com.android.tv.tuner.setup.BaseTunerSetupActivity;
import com.android.tv.tuner.setup.ConnectionTypeFragment;
import com.android.tv.tuner.setup.LineupFragment;
@@ -55,7 +56,7 @@
import java.util.ArrayList;
import java.util.List;
-/** An activity that serves Live TV tuner setup process. */
+/** An activity that serves Sample DVB tuner setup process. */
public class SampleDvbTunerSetupActivity extends BaseTunerSetupActivity {
private static final String TAG = "SampleDvbTunerSetupActivity";
private static final boolean DEBUG = false;
@@ -78,6 +79,10 @@
private final Runnable cancelFetchLineupTaskRunnable = this::cancelFetchLineup;
private String embeddedInputId;
+ public SampleDvbTunerSetupActivity() {
+ super(SampleDvbConstants.TUNER_INPUT_ID);
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -193,6 +198,7 @@
case ScanFragment.ACTION_CATEGORY:
switch (actionId) {
case ScanFragment.ACTION_CANCEL:
+ clearTunerHal();
getFragmentManager().popBackStack();
return true;
case ScanFragment.ACTION_FINISH:
diff --git a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/util/SampleDvbConstants.java b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/util/SampleDvbConstants.java
new file mode 100644
index 0000000..f7b34ce
--- /dev/null
+++ b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/util/SampleDvbConstants.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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.tuner.sample.dvb.util;
+
+/** Static constants for Sample DVB Tuner */
+public final class SampleDvbConstants {
+
+ /** The Input ID for the embedded tuner in Sample DVB Tuner */
+ public static final String TUNER_INPUT_ID =
+ "com.android.tv.tuner.sample.dvb/.tvinput.SampleDvbTunerTvInputService";
+
+ private SampleDvbConstants() {}
+}
diff --git a/tuner/SampleNetworkTuner/AndroidManifest.xml b/tuner/SampleNetworkTuner/AndroidManifest.xml
index 0ec9afc..22d0fe6 100755
--- a/tuner/SampleNetworkTuner/AndroidManifest.xml
+++ b/tuner/SampleNetworkTuner/AndroidManifest.xml
@@ -19,16 +19,19 @@
<uses-sdk
android:minSdkVersion="23"
- android:targetSdkVersion="27" />
+ android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_TV_LISTINGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+ <!-- Permission to modify Recorded Program -->
+ <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
<!-- Permissions/feature for USB tuner -->
<uses-permission android:name="android.permission.DVB_DEVICE" />
@@ -87,4 +90,4 @@
android:process="com.android.tv.tuner" />
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tuner/SampleNetworkTuner/build.gradle b/tuner/SampleNetworkTuner/build.gradle
index 657a425..28ad3e4 100644
--- a/tuner/SampleNetworkTuner/build.gradle
+++ b/tuner/SampleNetworkTuner/build.gradle
@@ -19,48 +19,35 @@
* Experimental gradle configuration. This file may not be up to date.
*/
-buildscript {
- repositories {
- mavenCentral()
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5'
- }
-}
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
-android {
- compileSdkVersion 26
- buildToolsVersion '28.0.2'
- dexOptions {
- preDexLibraries = false
- additionalParameters=['--core-library']
- javaMaxHeapSize "6g"
- }
- android {
- defaultConfig {
- resConfigs "en"
- }
- }
- defaultConfig {
- minSdkVersion 23
- targetSdkVersion 26
- versionCode 1
- versionName "1.0"
- }
- buildTypes {
- debug {
- minifyEnabled false
- }
- }
+android {
+ compileSdkVersion 28
+ buildToolsVersion '28.0.3'
+
compileOptions() {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
+
+ defaultConfig {
+ minSdkVersion 23
+ resConfigs "en"
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+
+ buildTypes {
+ debug {
+ minifyEnabled false
+ }
+ release {
+ minifyEnabled true
+ }
+ }
+
sourceSets {
main {
res.srcDirs = ['res']
@@ -70,22 +57,18 @@
}
}
-repositories {
- mavenCentral()
- jcenter()
- google()
-}
-
-final String SUPPORT_LIBS_VERSION = '26.1.0'
dependencies {
- implementation 'com.google.android.exoplayer:exoplayer-core:2.9.0'
- implementation 'com.google.android.exoplayer:exoplayer:r1.5.16'
- implementation "com.android.support:palette-v7:${SUPPORT_LIBS_VERSION}"
- implementation "com.android.support:leanback-v17:${SUPPORT_LIBS_VERSION}"
- implementation "com.android.support:support-tv-provider:${SUPPORT_LIBS_VERSION}"
- /*Not building with latest one (1.6.2)*/
- annotationProcessor 'com.google.auto.value:auto-value:1.5.4'
- implementation 'com.google.auto.value:auto-value:1.5.4'
- implementation project(':common')
- implementation project(':tuner')
+ implementation 'androidx.leanback:leanback:1.1.0-alpha02'
+ implementation 'androidx.palette:palette:1.0.0'
+ implementation 'androidx.tvprovider:tvprovider:1.0.0'
+
+ annotationProcessor 'com.google.auto.value:auto-value:1.5.3'
+ implementation 'com.google.auto.value:auto-value:1.5.3'
+ implementation 'com.google.dagger:dagger:2.23'
+ implementation 'com.google.dagger:dagger-android:2.23'
+ annotationProcessor 'com.google.dagger:dagger-android-processor:2.23'
+ annotationProcessor 'com.google.dagger:dagger-compiler:2.23'
+
+ implementation project(':common')
+ implementation project(':tuner')
}
diff --git a/tuner/SampleNetworkTuner/robotests/javatests/com/android/tv/tuner/sample/network/util/SampleNetworkConstantsTest.java b/tuner/SampleNetworkTuner/robotests/javatests/com/android/tv/tuner/sample/network/util/SampleNetworkConstantsTest.java
new file mode 100644
index 0000000..087a1a6
--- /dev/null
+++ b/tuner/SampleNetworkTuner/robotests/javatests/com/android/tv/tuner/sample/network/util/SampleNetworkConstantsTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.tuner.sample.network.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import androidx.test.core.app.ApplicationProvider;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.tuner.sample.network.tvinput.SampleNetworkTunerTvInputService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link SampleNetworkConstants}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class SampleNetworkConstantsTest {
+
+ @Test
+ public void tunerInputId() {
+ assertThat(ComponentName.unflattenFromString(SampleNetworkConstants.TUNER_INPUT_ID))
+ .isEqualTo(
+ new ComponentName(
+ ApplicationProvider.getApplicationContext(),
+ SampleNetworkTunerTvInputService.class));
+ }
+}
diff --git a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/AndroidManifest.xml b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/AndroidManifest.xml
index dddd8a4..ad0e751 100644
--- a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/AndroidManifest.xml
+++ b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/AndroidManifest.xml
@@ -36,7 +36,7 @@
<uses-feature android:name="android.software.leanback" android:required="true" />
<uses-feature android:name="android.software.live_tv" android:required="true" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application
android:name=".app.SampleNetworkTuner"
android:icon="@mipmap/ic_launcher"
diff --git a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/app/SampleNetworkTuner.java b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/app/SampleNetworkTuner.java
index eb5b2ad..105f560 100644
--- a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/app/SampleNetworkTuner.java
+++ b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/app/SampleNetworkTuner.java
@@ -18,16 +18,19 @@
import android.content.ComponentName;
import android.media.tv.TvContract;
+
import com.android.tv.common.BaseApplication;
+import com.android.tv.common.dagger.ApplicationModule;
import com.android.tv.common.singletons.HasSingletons;
import com.android.tv.tuner.modules.TunerSingletonsModule;
import com.android.tv.tuner.sample.network.singletons.SampleNetworkSingletons;
import com.android.tv.tuner.sample.network.tvinput.SampleNetworkTunerTvInputService;
-import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
-import com.android.tv.tuner.tvinput.factory.TunerSessionFactoryImpl;
+
import dagger.android.AndroidInjector;
+
import com.android.tv.common.flags.CloudEpgFlags;
import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
import javax.inject.Inject;
/** The top level application for Sample DVB Tuner. */
@@ -37,7 +40,6 @@
private String mEmbeddedInputId;
@Inject CloudEpgFlags mCloudEpgFlags;
@Inject ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
- @Inject TunerSessionFactoryImpl mTunerSessionFactory;
@Override
public void onCreate() {
@@ -47,7 +49,7 @@
@Override
protected AndroidInjector<SampleNetworkTuner> applicationInjector() {
return DaggerSampleNetworkTunerComponent.builder()
- .sampleNetworkTunerModule(new SampleNetworkTunerModule(this))
+ .applicationModule(new ApplicationModule(this))
.tunerSingletonsModule(new TunerSingletonsModule(this))
.build();
}
@@ -73,16 +75,7 @@
}
@Override
- public ConcurrentDvrPlaybackFlags getConcurrentDvrPlaybackFlags() {
- return mConcurrentDvrPlaybackFlags;
- }
-
- @Override
public SampleNetworkSingletons singletons() {
return this;
}
-
- public TunerSessionFactory getTunerSessionFactory() {
- return mTunerSessionFactory;
- }
}
diff --git a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/app/SampleNetworkTunerModule.java b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/app/SampleNetworkTunerModule.java
index d974e20..3fa4502 100644
--- a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/app/SampleNetworkTunerModule.java
+++ b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/app/SampleNetworkTunerModule.java
@@ -15,38 +15,30 @@
*/
package com.android.tv.tuner.sample.network.app;
+import com.android.tv.common.dagger.ApplicationModule;
import com.android.tv.common.flags.impl.DefaultFlagsModule;
import com.android.tv.tuner.api.TunerFactory;
-import com.android.tv.tuner.builtin.BuiltInTunerHalFactory;
+import com.android.tv.tuner.hdhomerun.HdHomeRunTunerHalFactory;
import com.android.tv.tuner.modules.TunerModule;
import com.android.tv.tuner.sample.network.setup.SampleNetworkTunerSetupActivity;
import com.android.tv.tuner.sample.network.tvinput.SampleNetworkTunerTvInputService;
-import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
+
import dagger.Module;
import dagger.Provides;
/** Dagger module for {@link SampleNetworkTuner}. */
@Module(
includes = {
+ ApplicationModule.class,
DefaultFlagsModule.class,
SampleNetworkTunerTvInputService.Module.class,
SampleNetworkTunerSetupActivity.Module.class,
TunerModule.class,
})
class SampleNetworkTunerModule {
- private final SampleNetworkTuner mSampleNetworkTuner;
-
- SampleNetworkTunerModule(SampleNetworkTuner sampleNetworkTuner) {
- mSampleNetworkTuner = sampleNetworkTuner;
- }
-
- @Provides
- public TunerSessionFactory providesTunerSessionFactory() {
- return mSampleNetworkTuner.getTunerSessionFactory();
- }
@Provides
TunerFactory providesTunerFactory() {
- return BuiltInTunerHalFactory.INSTANCE;
+ return HdHomeRunTunerHalFactory.INSTANCE;
}
}
diff --git a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/setup/SampleNetworkTunerSetupActivity.java b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/setup/SampleNetworkTunerSetupActivity.java
index fd783c4..755e0bb 100644
--- a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/setup/SampleNetworkTunerSetupActivity.java
+++ b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/setup/SampleNetworkTunerSetupActivity.java
@@ -36,6 +36,7 @@
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.common.util.PostalCodeUtils;
import com.android.tv.tuner.sample.network.R;
+import com.android.tv.tuner.sample.network.util.SampleNetworkConstants;
import com.android.tv.tuner.setup.BaseTunerSetupActivity;
import com.android.tv.tuner.setup.ConnectionTypeFragment;
import com.android.tv.tuner.setup.LineupFragment;
@@ -55,7 +56,7 @@
import java.util.ArrayList;
import java.util.List;
-/** An activity that serves Live TV tuner setup process. */
+/** An activity that serves TV app tuner setup process. */
public class SampleNetworkTunerSetupActivity extends BaseTunerSetupActivity {
private static final String TAG = "SampleNetworkTunerSetupActivity";
private static final boolean DEBUG = false;
@@ -78,6 +79,10 @@
private final Runnable cancelFetchLineupTaskRunnable = this::cancelFetchLineup;
private String embeddedInputId;
+ public SampleNetworkTunerSetupActivity() {
+ super(SampleNetworkConstants.TUNER_INPUT_ID);
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/util/SampleNetworkConstants.java b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/util/SampleNetworkConstants.java
new file mode 100644
index 0000000..d0a8d25
--- /dev/null
+++ b/tuner/SampleNetworkTuner/src/com/android/tv/tuner/sample/network/util/SampleNetworkConstants.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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.tuner.sample.network.util;
+
+/** Static constants for Sample Network Tuner */
+public final class SampleNetworkConstants {
+
+ /** The Input ID for the embedded tuner in Sample Network Tuner */
+ public static final String TUNER_INPUT_ID =
+ "com.android.tv.tuner.sample.network/.tvinput.SampleNetworkTunerTvInputService";
+
+ private SampleNetworkConstants() {}
+}
diff --git a/tuner/build.gradle b/tuner/build.gradle
index 0f40a29..c1001c8 100644
--- a/tuner/build.gradle
+++ b/tuner/build.gradle
@@ -21,39 +21,24 @@
apply plugin: 'com.android.library'
apply plugin: 'com.google.protobuf'
-buildscript {
- repositories {
- mavenCentral()
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5'
- }
-}
+
android {
- compileSdkVersion 26
- buildToolsVersion '28.0.2'
+ compileSdkVersion 28
+ buildToolsVersion '28.0.3'
- dexOptions {
- preDexLibraries = false
- additionalParameters = ['--core-library']
- javaMaxHeapSize "6g"
- }
-
- android {
- defaultConfig {
- resConfigs "en"
- }
+ compileOptions() {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 23
- targetSdkVersion 26
+ resConfigs "en"
+ targetSdkVersion 28
versionCode 1
versionName "1.0"
}
+
buildTypes {
debug {
minifyEnabled false
@@ -62,10 +47,6 @@
minifyEnabled true
}
}
- compileOptions() {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
sourceSets {
main {
@@ -73,43 +54,54 @@
java.srcDirs = ['src']
manifest.srcFile 'AndroidManifest.xml'
proto {
- srcDir 'proto/'
+ srcDir 'proto/src/main/proto'
}
}
}
}
-repositories {
- mavenCentral()
- google()
- jcenter()
-}
-
-final String SUPPORT_LIBS_VERSION = '26.1.0'
dependencies {
- implementation 'com.google.android.exoplayer:exoplayer-core:2.9.0'
- implementation 'com.google.android.exoplayer:exoplayer:r1.5.16'
- implementation "com.android.support:support-tv-provider:${SUPPORT_LIBS_VERSION}"
- implementation "com.android.support:appcompat-v7:${SUPPORT_LIBS_VERSION}"
- implementation "com.android.support:leanback-v17:${SUPPORT_LIBS_VERSION}"
- implementation 'com.google.guava:guava:26.0-android'
- implementation 'com.google.protobuf.nano:protobuf-javanano:3.2.0rc2'
- implementation project(':common')
+ implementation 'androidx.annotation:annotation:1.1.0'
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.leanback:leanback:1.1.0-alpha02'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
+ implementation 'androidx.tvprovider:tvprovider:1.0.0'
+
+ implementation 'com.google.android.exoplayer:exoplayer:r1.5.16'
+ implementation 'com.google.android.exoplayer:exoplayer-core:2.10.1'
+ annotationProcessor 'com.google.auto.factory:auto-factory:1.0-beta6'
+ implementation 'com.google.auto.factory:auto-factory:1.0-beta6'
+ implementation 'com.google.dagger:dagger:2.23'
+ implementation 'com.google.dagger:dagger-android:2.23'
+ annotationProcessor 'com.google.dagger:dagger-android-processor:2.23'
+ annotationProcessor 'com.google.dagger:dagger-compiler:2.23'
+ implementation 'com.google.guava:guava:28.0-jre'
+ implementation 'com.google.protobuf:protobuf-java:3.0.0'
+
+ implementation project(':common')
}
protobuf {
// Configure the protoc executable
protoc {
- artifact = 'com.google.protobuf:protoc:3.1.0'
+ artifact = 'com.google.protobuf:protoc:3.0.0'
+
+ plugins {
+ javalite {
+ // The codegen for lite comes as a separate artifact
+ artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
+ }
+ }
generateProtoTasks {
all().each {
task -> task.builtins {
remove java
- javanano {
- option "enum_style=java"
- }
}
+ task.plugins {
+ javalite {}
+ }
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tuner/proto/Android.bp b/tuner/proto/Android.bp
index 67f35f8..d1728a6 100644
--- a/tuner/proto/Android.bp
+++ b/tuner/proto/Android.bp
@@ -19,8 +19,7 @@
srcs: ["*.proto"],
sdk_version: "system_current",
proto: {
- type: "nano",
- output_params: ["enum_style=java"],
+ type: "lite",
canonical_path_from_root: false,
},
min_sdk_version: "23",
diff --git a/tuner/proto/channel.proto b/tuner/proto/channel.proto
index ff372ad..815ffbc 100644
--- a/tuner/proto/channel.proto
+++ b/tuner/proto/channel.proto
@@ -18,18 +18,13 @@
package com.android.tv.tuner.data;
+import "track.proto";
+
option java_package = "com.android.tv.tuner.data";
option java_outer_classname = "Channel";
-
-// AOSP_Comment_Out import "third_party/android/nanoproto/nano_descriptor.proto";
-
-import "track.proto";
-
// Holds information about a channel used in the tuners.
message TunerChannelProto {
-// AOSP_Comment_Out option (proto2.nano.message_as_lite) = false;
-
optional TunerType type = 1;
optional string short_name = 2;
optional string long_name = 3;
@@ -51,22 +46,24 @@
optional int32 audio_track_index = 19;
repeated AtscCaptionTrack caption_tracks = 20;
optional bool has_caption_track = 21;
- optional AtscServiceType service_type = 22 [default = SERVICE_TYPE_ATSC_DIGITAL_TELEVISION];
+ optional AtscServiceType service_type = 22
+ [default = SERVICE_TYPE_ATSC_DIGITAL_TELEVISION];
optional bool recording_prohibited = 23;
optional string video_format = 24;
/**
* The flag indicating whether this TV channel is locked or not.
- * This is primarily used for alternative parental control to prevent unauthorized users from
- * watching the current channel regardless of the content rating
- * @see <a href="https://developer.android.com/reference/android/media/tv/TvContract.Channels.html#COLUMN_LOCKED">link</a>
+ * This is primarily used for alternative parental control to prevent
+ * unauthorized users from watching the current channel regardless of the
+ * content rating
+ * @see <a
+ * href="https://developer.android.com/reference/android/media/tv/TvContract.Channels.html#COLUMN_LOCKED">link</a>
*/
optional bool locked = 25;
+ optional DeliverySystemType delivery_system_type = 26;
}
// Enum describing the types of tuner.
enum TunerType {
-// AOSP_Comment_Out option (proto2.nano.enum_as_lite) = false;
-
TYPE_TUNER = 0;
TYPE_FILE = 1;
TYPE_NETWORK = 2;
@@ -74,8 +71,10 @@
// Enum describing the types of video stream.
enum VideoStreamType {
-// AOSP_Comment_Out option (proto2.nano.enum_as_lite) = false;
-
+ // Default unset value. The spec says 0 is reserved.
+ UNSET = 0x00;
+ // DEPRECATED: previously used as default or unset value
+ INVALID_STREAMTYPE = -1 [deprecated=true];
// ISO/IEC 11172 Video (MPEG-1)
MPEG1 = 0x01;
// ISO/IEC 13818-2 (MPEG-2) Video
@@ -90,8 +89,6 @@
// Enum describing the types of audio stream.
enum AudioStreamType {
-// AOSP_Comment_Out option (proto2.nano.enum_as_lite) = false;
-
// ISO/IEC 11172 Audio (MPEG-1)
MPEG1AUDIO = 0x03;
// ISO/IEC 13818-3 Audio (MPEG-2)
@@ -109,8 +106,6 @@
// Enum describing ATSC service types
// See ATSC Code Points Registry.
enum AtscServiceType {
-// AOSP_Comment_Out option (proto2.nano.enum_as_lite) = false;
-
SERVICE_TYPE_ATSC_RESERVED = 0x0;
SERVICE_TYPE_ANALOG_TELEVISION_CHANNELS = 0x1;
SERVICE_TYPE_ATSC_DIGITAL_TELEVISION = 0x2;
@@ -122,3 +117,15 @@
SERVICE_TYPE_ATSC_NRT_SERVICE = 0x8;
SERVICE_TYPE_EXTENDED_PARAMERTERIZED_SERVICE = 0x9;
}
+
+// Enum describing the types of delivery system.
+enum DeliverySystemType {
+ // Do not reorder. Must match Tuner.java
+ DELIVERY_SYSTEM_UNDEFINED = 0;
+ DELIVERY_SYSTEM_ATSC = 1;
+ DELIVERY_SYSTEM_DVBC = 2;
+ DELIVERY_SYSTEM_DVBS = 3;
+ DELIVERY_SYSTEM_DVBS2 = 4;
+ DELIVERY_SYSTEM_DVBT = 5;
+ DELIVERY_SYSTEM_DVBT2 = 6;
+}
diff --git a/tuner/proto/track.proto b/tuner/proto/track.proto
index 11ca784..a4a20db 100644
--- a/tuner/proto/track.proto
+++ b/tuner/proto/track.proto
@@ -18,15 +18,11 @@
package com.android.tv.tuner.data;
-// AOSP_Comment_Out import "third_party/android/nanoproto/nano_descriptor.proto";
-
option java_package = "com.android.tv.tuner.data";
option java_outer_classname = "Track";
// Represents a AC3 audio track.
message AtscAudioTrack {
-// AOSP_Comment_Out option (proto2.nano.message_as_lite) = false;
-
optional string language = 1;
optional AudioType audio_type = 2;
optional int32 index = 3;
@@ -36,8 +32,6 @@
// Enum describing the types of a audio track.
// See ISO/IEC 138181-1:2000(e) Table 2-53.
enum AudioType {
-// AOSP_Comment_Out option (proto2.nano.enum_as_lite) = false;
-
AUDIOTYPE_UNDEFINED = 0;
AUDIOTYPE_CLEAN_EFFECTS = 1;
AUDIOTYPE_HEARING_IMPAIRED = 2;
@@ -47,11 +41,8 @@
// Represents a CEA-708 caption track.
message AtscCaptionTrack {
-// AOSP_Comment_Out option (proto2.nano.message_as_lite) = false;
-
optional string language = 1;
optional int32 service_number = 2;
optional bool easy_reader = 3;
optional bool wide_aspect_ratio = 4;
}
-
diff --git a/tuner/res/layout/guided_action_editable.xml b/tuner/res/layout/guided_action_editable.xml
index 84f56f8..4d3c87f 100644
--- a/tuner/res/layout/guided_action_editable.xml
+++ b/tuner/res/layout/guided_action_editable.xml
@@ -15,13 +15,13 @@
~ limitations under the License.
-->
-<android.support.v17.leanback.widget.GuidedActionItemContainer
+<androidx.leanback.widget.GuidedActionItemContainer
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/guidedactions_editable"
style="?attr/guidedActionItemContainerStyle"
android:layout_height="88dp">
- <android.support.v17.leanback.widget.NonOverlappingLinearLayout
+ <androidx.leanback.widget.NonOverlappingLinearLayout
android:id="@+id/guidedactions_item_content"
style="?attr/guidedActionItemContentStyle" >
@@ -32,10 +32,10 @@
android:textColor="@color/lb_guidedactions_item_unselected_text_color"
android:textSize="16sp" />
- <android.support.v17.leanback.widget.GuidedActionEditText
+ <androidx.leanback.widget.GuidedActionEditText
android:id="@+id/guidedactions_item_description"
style="?attr/guidedActionItemDescriptionStyle" />
- </android.support.v17.leanback.widget.NonOverlappingLinearLayout>
+ </androidx.leanback.widget.NonOverlappingLinearLayout>
-</android.support.v17.leanback.widget.GuidedActionItemContainer>
\ No newline at end of file
+</androidx.leanback.widget.GuidedActionItemContainer>
\ No newline at end of file
diff --git a/tuner/res/raw/ut_euro_dvbt_all b/tuner/res/raw/ut_euro_dvbt_all
index 101ee3e..a2ff03b 100644
--- a/tuner/res/raw/ut_euro_dvbt_all
+++ b/tuner/res/raw/ut_euro_dvbt_all
@@ -1,66 +1,6 @@
# Euro DVB-T frequencies
# Only Germany and England frequencies are added now.
-# Frequencies from Germany
-T 474000000 QAM16
-T 474000000 QAM64
-T 482000000 QAM16
-T 490000000 QAM16
-T 498000000 QAM16
-T 498000000 QAM64
-T 506000000 QAM16
-T 506000000 QAM64
-T 514000000 QAM16
-T 522000000 QAM16
-T 522000000 QAM64
-T 530000000 QAM16
-T 538000000 QAM16
-T 538000000 QAM64
-T 546000000 QAM16
-T 554000000 QAM16
-T 554000000 QAM64
-T 562000000 QAM16
-T 562000000 QAM64
-T 570000000 QAM16
-T 578000000 QAM16
-T 578000000 QAM64
-T 586000000 QAM16
-T 594000000 QAM16
-T 602000000 QAM16
-T 602000000 QAM64
-T 610000000 QAM16
-T 610000000 QAM64
-T 618000000 QAM16
-T 618000000 QAM64
-T 626000000 QAM16
-T 634000000 QAM16
-T 634000000 QAM64
-T 642000000 QAM16
-T 650000000 QAM16
-T 658000000 QAM16
-T 666000000 QAM16
-T 674000000 QAM16
-T 674000000 QAM64
-T 682000000 QAM16
-T 690000000 QAM16
-T 690000000 QAM64
-T 698000000 QAM16
-T 698000000 QAM64
-T 706000000 QAM16
-T 722000000 QAM16
-T 730000000 QAM16
-T 730000000 QAM64
-T 738000000 QAM16
-T 738000000 QAM64
-T 746000000 QAM16
-T 746000000 QPSK
-T 754000000 QAM16
-T 762000000 QAM16
-T 770000000 QAM16
-T 778000000 QAM16
-T 786000000 QAM16
-
-# Frequencies from England
T 474000000 QAM16
T 474000000 QAM64
T 474167000 QAM16
@@ -130,6 +70,7 @@
T 562167000 QAM16
T 569833000 QAM64
T 570000000 QAM16
+T 570000000 QAM64
T 570167000 QAM16
T 577833000 QAM16
T 578000000 QAM16
@@ -138,7 +79,9 @@
T 578167000 QAM16
T 578167000 QAM64
T 586000000 QAM16
+T 586000000 QAM256
T 594000000 QAM16
+T 594000000 QAM64
T 602000000 QAM16
T 602000000 QAM64
T 610000000 QAM16
@@ -224,11 +167,13 @@
T 746000000 QAM16
T 746000000 QAM64
T 746000000 QPSK
+T 746000000 QAM256
T 746167000 QAM64
T 753833000 QAM16
T 753833000 QAM64
T 754000000 QAM16
T 754000000 QAM64
+T 754000000 QAM256
T 754167000 QAM16
T 761833000 QAM16
T 761833000 QAM64
diff --git a/tuner/src/com/android/tv/tuner/TunerHal.java b/tuner/src/com/android/tv/tuner/TunerHal.java
index dce4f4c..3f469d6 100644
--- a/tuner/src/com/android/tv/tuner/TunerHal.java
+++ b/tuner/src/com/android/tv/tuner/TunerHal.java
@@ -36,6 +36,7 @@
private static final int DEFAULT_QAM_TUNE_TIMEOUT_MS = 4000; // Some device takes time for
@DeliverySystemType private int mDeliverySystemType;
+ @DeliverySystemType private int[] mDeliverySystemTypes;
private boolean mIsStreaming;
private int mFrequency;
private String mModulation;
@@ -57,9 +58,16 @@
}
protected void getDeliverySystemTypeFromDevice() {
+ getDeliverySystemTypesFromDevice();
+ }
+
+ protected void getDeliverySystemTypesFromDevice() {
if (mDeliverySystemType == DELIVERY_SYSTEM_UNDEFINED) {
mDeliverySystemType = nativeGetDeliverySystemType(getDeviceId());
}
+ if (mDeliverySystemTypes == null) {
+ mDeliverySystemTypes = nativeGetDeliverySystemTypes(getDeviceId());
+ }
}
/**
@@ -79,18 +87,34 @@
protected native void nativeFinalize(long deviceId);
+ @Override
+ public synchronized boolean tune(
+ int frequency, @ModulationType String modulation,
+ String channelNumber) {
+ return tuneInternal(mDeliverySystemType, frequency, modulation, channelNumber);
+ }
+
+ @Override
+ public synchronized boolean tune(
+ int deliverySystemType, int frequency, @ModulationType String modulation,
+ String channelNumber) {
+ return tuneInternal(deliverySystemType, frequency, modulation, channelNumber);
+ }
+
/**
* Sets the tuner channel. This should be called after acquiring a tuner device.
*
+ * @param deliverySystemType a system delivery type of the channel to tune to
* @param frequency a frequency of the channel to tune to
* @param modulation a modulation method of the channel to tune to
* @param channelNumber channel number when channel number is already known. Some tuner HAL may
* use channelNumber instead of frequency for tune.
* @return {@code true} if the operation was successful, {@code false} otherwise
*/
- @Override
- public synchronized boolean tune(
- int frequency, @ModulationType String modulation, String channelNumber) {
+ protected boolean tuneInternal(
+ int deliverySystemType, int frequency, @ModulationType String modulation,
+ String channelNumber) {
+
if (!isDeviceOpen()) {
Log.e(TAG, "There's no available device");
return false;
@@ -99,40 +123,76 @@
nativeCloseAllPidFilters(getDeviceId());
mIsStreaming = false;
}
+ if (mDeliverySystemTypes != null) {
+ int i;
+ for (i = 0; i < mDeliverySystemTypes.length; i++) {
+ if (deliverySystemType == mDeliverySystemTypes[i]) {
+ break;
+ }
+ }
+
+ if (i == mDeliverySystemTypes.length) {
+ Log.e(TAG, "Unsupported delivery system type for device");
+ return false;
+ }
+ }
// When tuning to a new channel in the same frequency, there's no need to stop current tuner
// device completely and the only thing necessary for tuning is reopening pid filters.
if (mFrequency == frequency && Objects.equals(mModulation, modulation)) {
addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
- if (Tuner.isDvbDeliverySystem(mDeliverySystemType)) {
+ if (Tuner.isDvbDeliverySystem(deliverySystemType)) {
addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER);
addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER);
}
mIsStreaming = true;
return true;
}
+
int timeout_ms =
modulation.equals(MODULATION_8VSB)
? DEFAULT_VSB_TUNE_TIMEOUT_MS
: DEFAULT_QAM_TUNE_TIMEOUT_MS;
- if (nativeTune(getDeviceId(), frequency, modulation, timeout_ms)) {
+
+ boolean tuneStatus;
+ switch(deliverySystemType) {
+ case DELIVERY_SYSTEM_UNDEFINED:
+ case DELIVERY_SYSTEM_ATSC:
+ tuneStatus = nativeTune(getDeviceId(), frequency, modulation, timeout_ms);
+ break;
+ case DELIVERY_SYSTEM_DVBT:
+ case DELIVERY_SYSTEM_DVBT2:
+ tuneStatus = nativeTune(getDeviceId(), deliverySystemType, frequency, modulation,
+ timeout_ms);
+ break;
+ default:
+ Log.e(TAG, "Unsupported delivery system type for device");
+ return false;
+ }
+
+ if (tuneStatus == true) {
addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
- if (Tuner.isDvbDeliverySystem(mDeliverySystemType)) {
+ if (Tuner.isDvbDeliverySystem(deliverySystemType)) {
addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER);
addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER);
}
mFrequency = frequency;
mModulation = modulation;
mIsStreaming = true;
- return true;
}
- return false;
+
+ return tuneStatus;
}
protected native boolean nativeTune(
- long deviceId, int frequency, @ModulationType String modulation, int timeout_ms);
+ long deviceId, int frequency,
+ @ModulationType String modulation, int timeout_ms);
+
+ protected native boolean nativeTune(
+ long deviceId, int deliverySystemType, int frequency,
+ @ModulationType String modulation, int timeout_ms);
/**
* Sets a pid filter. This should be set after setting a channel.
@@ -162,6 +222,8 @@
protected native int nativeGetDeliverySystemType(long deviceId);
+ protected native int[] nativeGetDeliverySystemTypes(long deviceId);
+
protected native int nativeGetSignalStrength(long deviceId);
/**
@@ -191,6 +253,11 @@
return mDeliverySystemType;
}
+ @Override
+ public int[] getDeliverySystemTypes() {
+ return mDeliverySystemTypes;
+ }
+
protected native void nativeStopTune(long deviceId);
/**
diff --git a/tuner/src/com/android/tv/tuner/api/ScanChannel.java b/tuner/src/com/android/tv/tuner/api/ScanChannel.java
index 56e5493..1c7a6e7 100644
--- a/tuner/src/com/android/tv/tuner/api/ScanChannel.java
+++ b/tuner/src/com/android/tv/tuner/api/ScanChannel.java
@@ -15,11 +15,15 @@
*/
package com.android.tv.tuner.api;
-import com.android.tv.tuner.data.nano.Channel;
+import android.util.Log;
+import com.android.tv.tuner.data.Channel;
+
/** Channel information gathered from a <em>scan</em> */
public final class ScanChannel {
+ private static final String TAG = "ScanChannel";
public final int type;
+ public final Channel.DeliverySystemType deliverySystemType;
public final int frequency;
public final String modulation;
public final String filename;
@@ -31,25 +35,60 @@
public final Integer radioFrequencyNumber;
public static ScanChannel forTuner(
- int frequency, String modulation, Integer radioFrequencyNumber) {
+ String deliverySystemType, int frequency, String modulation,
+ Integer radioFrequencyNumber) {
return new ScanChannel(
- Channel.TunerType.TYPE_TUNER, frequency, modulation, null, radioFrequencyNumber);
+ Channel.TunerType.TYPE_TUNER_VALUE, lookupDeliveryStringToInt(deliverySystemType),
+ frequency, modulation, null, radioFrequencyNumber);
}
public static ScanChannel forFile(int frequency, String filename) {
- return new ScanChannel(Channel.TunerType.TYPE_FILE, frequency, "file:", filename, null);
+ return new ScanChannel(Channel.TunerType.TYPE_FILE_VALUE,
+ Channel.DeliverySystemType.DELIVERY_SYSTEM_UNDEFINED, frequency, "file:",
+ filename, null);
}
private ScanChannel(
int type,
+ Channel.DeliverySystemType deliverySystemType,
int frequency,
String modulation,
String filename,
Integer radioFrequencyNumber) {
this.type = type;
+ this.deliverySystemType = deliverySystemType;
this.frequency = frequency;
this.modulation = modulation;
this.filename = filename;
this.radioFrequencyNumber = radioFrequencyNumber;
}
+
+ private static Channel.DeliverySystemType lookupDeliveryStringToInt(String deliverySystemType) {
+ Channel.DeliverySystemType ret;
+ switch (deliverySystemType) {
+ case "A":
+ ret = Channel.DeliverySystemType.DELIVERY_SYSTEM_ATSC;
+ break;
+ case "C":
+ ret = Channel.DeliverySystemType.DELIVERY_SYSTEM_DVBC;
+ break;
+ case "S":
+ ret = Channel.DeliverySystemType.DELIVERY_SYSTEM_DVBS;
+ break;
+ case "S2":
+ ret = Channel.DeliverySystemType.DELIVERY_SYSTEM_DVBS2;
+ break;
+ case "T":
+ ret = Channel.DeliverySystemType.DELIVERY_SYSTEM_DVBT;
+ break;
+ case "T2":
+ ret = Channel.DeliverySystemType.DELIVERY_SYSTEM_DVBT2;
+ break;
+ default:
+ Log.e(TAG, "Unknown delivery system type");
+ ret = Channel.DeliverySystemType.DELIVERY_SYSTEM_UNDEFINED;
+ break;
+ }
+ return ret;
+ }
}
diff --git a/tuner/src/com/android/tv/tuner/api/Tuner.java b/tuner/src/com/android/tv/tuner/api/Tuner.java
index 6f7e9d9..02df3ca 100644
--- a/tuner/src/com/android/tv/tuner/api/Tuner.java
+++ b/tuner/src/com/android/tv/tuner/api/Tuner.java
@@ -28,6 +28,8 @@
int FILTER_TYPE_VIDEO = 2;
int FILTER_TYPE_PCR = 3;
String MODULATION_8VSB = "8VSB";
+ String MODULATION_QAM16 = "QAM16";
+ String MODULATION_QAM64 = "QAM64";
String MODULATION_QAM256 = "QAM256";
int DELIVERY_SYSTEM_UNDEFINED = 0;
int DELIVERY_SYSTEM_ATSC = 1;
@@ -40,6 +42,7 @@
int TUNER_TYPE_USB = 2;
int TUNER_TYPE_NETWORK = 3;
int BUILT_IN_TUNER_TYPE_LINUX_DVB = 1;
+ int BUILT_IN_TUNER_TYPE_ARCHER = 100;
/** Check a delivery system is for DVB or not. */
static boolean isDvbDeliverySystem(@DeliverySystemType int deliverySystemType) {
@@ -66,6 +69,11 @@
boolean tune(int frequency, @ModulationType String modulation, String channelNumber);
+ default boolean tune(@DeliverySystemType int deliverySystemType, int frequency,
+ @ModulationType String modulation, String channelNumber) {
+ return tune(frequency, modulation, channelNumber);
+ }
+
boolean addPidFilter(int pid, @FilterType int filterType);
void stopTune();
@@ -73,6 +81,10 @@
void setHasPendingTune(boolean hasPendingTune);
int getDeliverySystemType();
+ default int[] getDeliverySystemTypes() {
+ int[] deliverySystemTypes = {DELIVERY_SYSTEM_UNDEFINED};
+ return deliverySystemTypes;
+ };
int readTsStream(byte[] javaBuffer, int javaBufferSize);
@@ -84,7 +96,7 @@
public @interface FilterType {}
/** Modulation Type */
- @StringDef({MODULATION_8VSB, MODULATION_QAM256})
+ @StringDef({MODULATION_8VSB, MODULATION_QAM256, MODULATION_QAM16, MODULATION_QAM64})
@Retention(RetentionPolicy.SOURCE)
public @interface ModulationType {}
@@ -108,6 +120,7 @@
/** Built in tuner type */
@IntDef({
+ BUILT_IN_TUNER_TYPE_ARCHER,
BUILT_IN_TUNER_TYPE_LINUX_DVB
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/tuner/src/com/android/tv/tuner/cc/CaptionLayout.java b/tuner/src/com/android/tv/tuner/cc/CaptionLayout.java
index eb9ad46..62a4e15 100644
--- a/tuner/src/com/android/tv/tuner/cc/CaptionLayout.java
+++ b/tuner/src/com/android/tv/tuner/cc/CaptionLayout.java
@@ -18,7 +18,7 @@
import android.content.Context;
import android.util.AttributeSet;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.layout.ScaledLayout;
/**
diff --git a/tuner/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java b/tuner/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
index 4a1c7c1..75776d6 100644
--- a/tuner/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
+++ b/tuner/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
@@ -27,7 +27,7 @@
import com.android.tv.tuner.data.Cea708Data.CaptionWindow;
import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr;
import com.android.tv.tuner.data.Cea708Parser;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import java.util.ArrayList;
/** Decodes and renders CEA-708. */
@@ -89,7 +89,7 @@
return;
}
if (DEBUG) {
- Log.d(TAG, "Start captionTrack " + captionTrack.language);
+ Log.d(TAG, "Start captionTrack " + captionTrack.getLanguage());
}
reset();
mCaptionLayout.setCaptionTrack(captionTrack);
diff --git a/tuner/src/com/android/tv/tuner/cc/CaptionWindowLayout.java b/tuner/src/com/android/tv/tuner/cc/CaptionWindowLayout.java
index 13c6ff4..8c699d5 100644
--- a/tuner/src/com/android/tv/tuner/cc/CaptionWindowLayout.java
+++ b/tuner/src/com/android/tv/tuner/cc/CaptionWindowLayout.java
@@ -459,14 +459,14 @@
private boolean isKoreanLanguageTrack() {
return mCaptionLayout != null
&& mCaptionLayout.getCaptionTrack() != null
- && mCaptionLayout.getCaptionTrack().language != null
- && "KOR".compareToIgnoreCase(mCaptionLayout.getCaptionTrack().language) == 0;
+ && mCaptionLayout.getCaptionTrack().hasLanguage()
+ && "KOR".equalsIgnoreCase(mCaptionLayout.getCaptionTrack().getLanguage());
}
private boolean isWideAspectRatio() {
return mCaptionLayout != null
&& mCaptionLayout.getCaptionTrack() != null
- && mCaptionLayout.getCaptionTrack().wideAspectRatio;
+ && mCaptionLayout.getCaptionTrack().getWideAspectRatio();
}
private void updateWidestChar() {
diff --git a/tuner/src/com/android/tv/tuner/data/Cea708Parser.java b/tuner/src/com/android/tv/tuner/data/Cea708Parser.java
index 92834b2..7a5538c 100644
--- a/tuner/src/com/android/tv/tuner/data/Cea708Parser.java
+++ b/tuner/src/com/android/tv/tuner/data/Cea708Parser.java
@@ -138,6 +138,7 @@
private long mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
private int mCommand = 0;
private int mListenServiceNumber = 0;
+ private int mDtvCcPacketCalculatedSize = 0;
private boolean mDtvCcPacking = false;
private boolean mFirstServiceNumberDiscovered;
@@ -229,6 +230,7 @@
mBuffer.setLength(0);
mDiscoveredNumBytes.clear();
mCommand = 0;
+ mDtvCcPacketCalculatedSize = 0;
mDtvCcPacking = false;
}
@@ -284,29 +286,33 @@
for (int i = 0; i < ccPacket.ccCount; ++i) {
boolean ccValid = (bytes[pos] & 0x04) != 0;
int ccType = bytes[pos] & 0x03;
-
- // The dtvcc should be considered complete:
- // - if either ccValid is set and ccType is 3
- // - or ccValid is clear and ccType is 2 or 3.
if (ccValid) {
+ // The dtvcc should be considered complete:
+ // if ccType is 3 or if the packet size is reached.
if (ccType == CC_TYPE_DTVCC_PACKET_START) {
if (mDtvCcPacking) {
parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
mDtvCcPacket.clear();
+ mDtvCcPacketCalculatedSize = 0;
}
mDtvCcPacking = true;
+ int packetSize = bytes[pos + 1] & 0x3F; // last 6 bits
+ if (packetSize == 0) {
+ packetSize = DTVCC_MAX_PACKET_SIZE;
+ }
+ mDtvCcPacketCalculatedSize = packetSize * DTVCC_PACKET_SIZE_SCALE_FACTOR;
mDtvCcPacket.append(bytes[pos + 1]);
mDtvCcPacket.append(bytes[pos + 2]);
} else if (mDtvCcPacking && ccType == CC_TYPE_DTVCC_PACKET_DATA) {
mDtvCcPacket.append(bytes[pos + 1]);
mDtvCcPacket.append(bytes[pos + 2]);
}
- } else {
if ((ccType == CC_TYPE_DTVCC_PACKET_START || ccType == CC_TYPE_DTVCC_PACKET_DATA)
- && mDtvCcPacking) {
+ && mDtvCcPacking && mDtvCcPacket.length() == mDtvCcPacketCalculatedSize) {
mDtvCcPacking = false;
parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
mDtvCcPacket.clear();
+ mDtvCcPacketCalculatedSize = 0;
}
}
pos += 3;
diff --git a/tuner/src/com/android/tv/tuner/data/PsiData.java b/tuner/src/com/android/tv/tuner/data/PsiData.java
index 9b7c2e2..74f1603 100644
--- a/tuner/src/com/android/tv/tuner/data/PsiData.java
+++ b/tuner/src/com/android/tv/tuner/data/PsiData.java
@@ -16,8 +16,8 @@
package com.android.tv.tuner.data;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import java.util.List;
/** Collection of MPEG PSI table items. */
diff --git a/tuner/src/com/android/tv/tuner/data/PsipData.java b/tuner/src/com/android/tv/tuner/data/PsipData.java
index d4af093..108ce3f 100644
--- a/tuner/src/com/android/tv/tuner/data/PsipData.java
+++ b/tuner/src/com/android/tv/tuner/data/PsipData.java
@@ -20,8 +20,8 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
import com.android.tv.common.util.StringUtils;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.util.ConvertUtils;
import java.util.ArrayList;
import java.util.HashMap;
@@ -495,10 +495,10 @@
public String toString() {
return String.format(
Locale.US,
- "AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, "
- + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, "
- + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s"
- + ", language2: %s",
+ "AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, surroundMode:"
+ + " %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, langCod2:"
+ + " %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s,"
+ + " language2: %s",
mSampleRateCode,
mBsid,
mBitRateCode,
@@ -832,7 +832,7 @@
}
ArrayList<String> languages = new ArrayList<>();
for (AtscAudioTrack audioTrack : mAudioTracks) {
- languages.add(audioTrack.language);
+ languages.add(audioTrack.getLanguage());
}
return TextUtils.join(",", languages);
}
diff --git a/tuner/src/com/android/tv/tuner/data/SectionParser.java b/tuner/src/com/android/tv/tuner/data/SectionParser.java
index d3dba6b..3c16749 100644
--- a/tuner/src/com/android/tv/tuner/data/SectionParser.java
+++ b/tuner/src/com/android/tv/tuner/data/SectionParser.java
@@ -24,7 +24,8 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
-
+import com.android.tv.common.feature.Model;
+import com.android.tv.tuner.data.Channel.AtscServiceType;
import com.android.tv.tuner.data.PsiData.PatItem;
import com.android.tv.tuner.data.PsiData.PmtItem;
import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor;
@@ -45,9 +46,8 @@
import com.android.tv.tuner.data.PsipData.ShortEventDescriptor;
import com.android.tv.tuner.data.PsipData.TsDescriptor;
import com.android.tv.tuner.data.PsipData.VctItem;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.util.ByteArrayBuffer;
import com.android.tv.tuner.util.ConvertUtils;
import java.io.UnsupportedEncodingException;
@@ -105,7 +105,7 @@
private static final int RATING_REGION_US_TV = 1;
private static final int RATING_REGION_KR_TV = 4;
- // The following values are defined in the live channels app.
+ // The following values are defined in the TV app.
// See https://developer.android.com/reference/android/media/tv/TvContentRating.html.
private static final String RATING_DOMAIN = "com.android.tv";
private static final String RATING_REGION_RATING_SYSTEM_US_TV = "US_TV";
@@ -916,8 +916,8 @@
Log.d(
TAG,
String.format(
- "Found channel [%s] %s - serviceType: %d tsid: 0x%x program: %d "
- + "channel: %d-%d encrypted: %b hidden: %b, descriptors: %d",
+ "Found channel [%s] %s - serviceType: %d tsid: 0x%x program: %d"
+ + " channel: %d-%d encrypted: %b hidden: %b, descriptors: %d",
shortName,
longName,
serviceType,
@@ -929,14 +929,14 @@
hidden,
descriptors.size()));
}
- if (!accessControlled
- && !hidden
- && (serviceType == Channel.AtscServiceType.SERVICE_TYPE_ATSC_AUDIO
+ if ((serviceType == AtscServiceType.SERVICE_TYPE_ATSC_AUDIO_VALUE
|| serviceType
- == Channel.AtscServiceType.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION
+ == AtscServiceType.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION_VALUE
|| serviceType
- == Channel.AtscServiceType
- .SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE)) {
+ == AtscServiceType
+ .SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE_VALUE)
+ && !accessControlled
+ && !hidden) {
// Hide hidden, encrypted, or unsupported ATSC service type channels
results.add(
new VctItem(
@@ -1212,16 +1212,20 @@
for (TsDescriptor descriptor : descriptors) {
if (descriptor instanceof Ac3AudioDescriptor) {
Ac3AudioDescriptor audioDescriptor = (Ac3AudioDescriptor) descriptor;
- AtscAudioTrack audioTrack = new AtscAudioTrack();
+ String language = null;
if (audioDescriptor.getLanguage() != null) {
- audioTrack.language = audioDescriptor.getLanguage();
+ language = audioDescriptor.getLanguage();
}
- if (audioTrack.language == null) {
- audioTrack.language = "";
+ if (language == null) {
+ language = "";
}
- audioTrack.audioType = AtscAudioTrack.AudioType.AUDIOTYPE_UNDEFINED;
- audioTrack.channelCount = audioDescriptor.getNumChannels();
- audioTrack.sampleRate = audioDescriptor.getSampleRate();
+ AtscAudioTrack audioTrack =
+ AtscAudioTrack.newBuilder()
+ .setLanguage(language)
+ .setAudioType(AtscAudioTrack.AudioType.AUDIOTYPE_UNDEFINED)
+ .setChannelCount(audioDescriptor.getNumChannels())
+ .setSampleRate(audioDescriptor.getSampleRate())
+ .build();
ac3Tracks.add(audioTrack);
}
}
@@ -1254,26 +1258,27 @@
}
int size = Math.max(ac3Tracks.size(), iso639LanguageTracks.size());
for (int i = 0; i < size; ++i) {
- AtscAudioTrack audioTrack = null;
+ AtscAudioTrack.Builder audioTrack = null;
if (i < ac3Tracks.size()) {
- audioTrack = ac3Tracks.get(i);
+ audioTrack = ac3Tracks.get(i).toBuilder();
}
if (i < iso639LanguageTracks.size()) {
if (audioTrack == null) {
- audioTrack = iso639LanguageTracks.get(i);
+ audioTrack = iso639LanguageTracks.get(i).toBuilder();
} else {
AtscAudioTrack iso639LanguageTrack = iso639LanguageTracks.get(i);
- if (audioTrack.language == null || TextUtils.equals(audioTrack.language, "")) {
- audioTrack.language = iso639LanguageTrack.language;
+ if (!audioTrack.hasLanguage()
+ || TextUtils.equals(audioTrack.getLanguage(), "")) {
+ audioTrack.setLanguage(iso639LanguageTrack.getLanguage());
}
- audioTrack.audioType = iso639LanguageTrack.audioType;
+ audioTrack.setAudioType(iso639LanguageTrack.getAudioType());
}
}
- String language = ISO_LANGUAGE_CODE_MAP.get(audioTrack.language);
+ String language = ISO_LANGUAGE_CODE_MAP.get(audioTrack.getLanguage());
if (language != null) {
- audioTrack.language = language;
+ audioTrack = audioTrack.setLanguage(language);
}
- tracks.add(audioTrack);
+ tracks.add(audioTrack.build());
}
return tracks;
}
@@ -1592,10 +1597,16 @@
return null;
}
String language = new String(data, pos, 3);
- int audioType = data[pos + 3] & 0xff;
- AtscAudioTrack audioTrack = new AtscAudioTrack();
- audioTrack.language = language;
- audioTrack.audioType = audioType;
+ int audioTypeInt = data[pos + 3] & 0xff;
+ AtscAudioTrack.AudioType audioType = AtscAudioTrack.AudioType.forNumber(audioTypeInt);
+ if (audioType == null) {
+ audioType = AtscAudioTrack.AudioType.AUDIOTYPE_UNDEFINED;
+ }
+ AtscAudioTrack audioTrack =
+ AtscAudioTrack.newBuilder()
+ .setLanguage(language)
+ .setAudioType(audioType)
+ .build();
audioTracks.add(audioTrack);
pos += 4;
}
@@ -1634,11 +1645,13 @@
reserved[0] |= (byte) ((data[pos + 1] & 0xc0) >>> 6);
reserved[1] = (byte) ((data[pos + 1] & 0x3f) << 2);
pos += 2;
- AtscCaptionTrack captionTrack = new AtscCaptionTrack();
- captionTrack.language = language;
- captionTrack.serviceNumber = captionServiceNumber;
- captionTrack.easyReader = easyReader;
- captionTrack.wideAspectRatio = wideAspectRatio;
+ AtscCaptionTrack captionTrack =
+ AtscCaptionTrack.newBuilder()
+ .setLanguage(language)
+ .setServiceNumber(captionServiceNumber)
+ .setEasyReader(easyReader)
+ .setWideAspectRatio(wideAspectRatio)
+ .build();
services.add(captionTrack);
}
return new CaptionServiceDescriptor(services);
@@ -2075,6 +2088,11 @@
}
private static boolean checkSanity(byte[] data) {
+ // Skipping CRC checking on Archer since TS data here was modified without updating CRC
+ // value. For details, see b/28616908.
+ if (Model.ARCHER.isEnabled()) {
+ return true;
+ }
if (data.length <= 1) {
return false;
}
diff --git a/tuner/src/com/android/tv/tuner/data/TunerChannel.java b/tuner/src/com/android/tv/tuner/data/TunerChannel.java
index d20c343..5872cd5 100644
--- a/tuner/src/com/android/tv/tuner/data/TunerChannel.java
+++ b/tuner/src/com/android/tv/tuner/data/TunerChannel.java
@@ -20,12 +20,10 @@
import android.support.annotation.NonNull;
import android.util.Log;
import com.android.tv.common.util.StringUtils;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Channel.TunerChannelProto;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.util.Ints;
-import com.google.protobuf.nano.MessageNano;
+import com.android.tv.tuner.data.Channel.DeliverySystemType;
+import com.android.tv.tuner.data.Channel.TunerChannelProto;
+import com.android.tv.tuner.data.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -55,7 +53,7 @@
"Extended Parameterized Service"
};
private static final String ATSC_SERVICE_TYPE_NAME_RESERVED =
- ATSC_SERVICE_TYPE_NAMES[Channel.AtscServiceType.SERVICE_TYPE_ATSC_RESERVED];
+ ATSC_SERVICE_TYPE_NAMES[Channel.AtscServiceType.SERVICE_TYPE_ATSC_RESERVED_VALUE];
public static final int INVALID_FREQUENCY = -1;
@@ -66,93 +64,128 @@
public static final int INVALID_STREAMTYPE = -1;
// @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766
- private final TunerChannelProto mProto;
+ private TunerChannelProto mProto;
private TunerChannel(
- PsipData.VctItem channel, int programNumber, List<PsiData.PmtItem> pmtItems, int type) {
- mProto = new TunerChannelProto();
- if (channel == null) {
- mProto.shortName = "";
- mProto.tsid = 0;
- mProto.programNumber = programNumber;
- mProto.virtualMajor = 0;
- mProto.virtualMinor = 0;
- } else {
- mProto.shortName = channel.getShortName();
- if (channel.getLongName() != null) {
- mProto.longName = channel.getLongName();
+ PsipData.VctItem channel,
+ int programNumber,
+ List<PsiData.PmtItem> pmtItems,
+ Channel.TunerType type) {
+ String shortName = "";
+ String longName = "";
+ String description = "";
+ int tsid = 0;
+ int virtualMajor = 0;
+ int virtualMinor = 0;
+ Channel.AtscServiceType serviceType =
+ Channel.AtscServiceType.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION;
+ if (channel != null) {
+ shortName = channel.getShortName();
+ tsid = channel.getChannelTsid();
+ programNumber = channel.getProgramNumber();
+ virtualMajor = channel.getMajorChannelNumber();
+ virtualMinor = channel.getMinorChannelNumber();
+ Channel.AtscServiceType chanServiceType =
+ Channel.AtscServiceType.forNumber(channel.getServiceType());
+ if (chanServiceType != null) {
+ serviceType = chanServiceType;
}
- mProto.tsid = channel.getChannelTsid();
- mProto.programNumber = channel.getProgramNumber();
- mProto.virtualMajor = channel.getMajorChannelNumber();
- mProto.virtualMinor = channel.getMinorChannelNumber();
- if (channel.getDescription() != null) {
- mProto.description = channel.getDescription();
- }
- mProto.serviceType = channel.getServiceType();
+ longName = (channel.getLongName() != null ? channel.getLongName() : longName);
+ description =
+ (channel.getDescription() != null ? channel.getDescription() : description);
}
- initProto(pmtItems, type);
+ TunerChannelProto tunerChannelProto =
+ TunerChannelProto.newBuilder()
+ .setShortName(shortName)
+ .setTsid(tsid)
+ .setProgramNumber(programNumber)
+ .setVirtualMajor(virtualMajor)
+ .setVirtualMinor(virtualMinor)
+ .setServiceType(serviceType)
+ .setLongName(longName)
+ .setDescription(description)
+ .build();
+ initProto(pmtItems, type, tunerChannelProto);
}
- private void initProto(List<PsiData.PmtItem> pmtItems, int type) {
- mProto.type = type;
- mProto.channelId = -1L;
- mProto.frequency = INVALID_FREQUENCY;
- mProto.videoPid = INVALID_PID;
- mProto.videoStreamType = INVALID_STREAMTYPE;
+ private void initProto(
+ List<PsiData.PmtItem> pmtItems,
+ Channel.TunerType type,
+ TunerChannelProto tunerChannelProto) {
+ int videoPid = INVALID_PID;
+ int pcrPid = 0;
+ Channel.VideoStreamType videoStreamType = Channel.VideoStreamType.UNSET;
List<Integer> audioPids = new ArrayList<>();
- List<Integer> audioStreamTypes = new ArrayList<>();
+ List<Channel.AudioStreamType> audioStreamTypes = new ArrayList<>();
for (PsiData.PmtItem pmt : pmtItems) {
switch (pmt.getStreamType()) {
// MPEG ES stream video types
- case Channel.VideoStreamType.MPEG1:
- case Channel.VideoStreamType.MPEG2:
- case Channel.VideoStreamType.H263:
- case Channel.VideoStreamType.H264:
- case Channel.VideoStreamType.H265:
- mProto.videoPid = pmt.getEsPid();
- mProto.videoStreamType = pmt.getStreamType();
+ case Channel.VideoStreamType.MPEG1_VALUE:
+ case Channel.VideoStreamType.MPEG2_VALUE:
+ case Channel.VideoStreamType.H263_VALUE:
+ case Channel.VideoStreamType.H264_VALUE:
+ case Channel.VideoStreamType.H265_VALUE:
+ videoPid = pmt.getEsPid();
+ videoStreamType = Channel.VideoStreamType.forNumber(pmt.getStreamType());
break;
// MPEG ES stream audio types
- case Channel.AudioStreamType.MPEG1AUDIO:
- case Channel.AudioStreamType.MPEG2AUDIO:
- case Channel.AudioStreamType.MPEG2AACAUDIO:
- case Channel.AudioStreamType.MPEG4LATMAACAUDIO:
- case Channel.AudioStreamType.A52AC3AUDIO:
- case Channel.AudioStreamType.EAC3AUDIO:
+ case Channel.AudioStreamType.MPEG1AUDIO_VALUE:
+ case Channel.AudioStreamType.MPEG2AUDIO_VALUE:
+ case Channel.AudioStreamType.MPEG2AACAUDIO_VALUE:
+ case Channel.AudioStreamType.MPEG4LATMAACAUDIO_VALUE:
+ case Channel.AudioStreamType.A52AC3AUDIO_VALUE:
+ case Channel.AudioStreamType.EAC3AUDIO_VALUE:
audioPids.add(pmt.getEsPid());
- audioStreamTypes.add(pmt.getStreamType());
+ audioStreamTypes.add(Channel.AudioStreamType.forNumber(pmt.getStreamType()));
break;
// Non MPEG ES stream types
case 0x100: // PmtItem.ES_PID_PCR:
- mProto.pcrPid = pmt.getEsPid();
+ pcrPid = pmt.getEsPid();
break;
default:
// fall out
}
}
- mProto.audioPids = Ints.toArray(audioPids);
- mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
- mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1;
+ mProto =
+ TunerChannelProto.newBuilder(tunerChannelProto)
+ .setType(type)
+ .setChannelId(-1L)
+ .setFrequency(INVALID_FREQUENCY)
+ .setVideoPid(videoPid)
+ .setVideoStreamType(videoStreamType)
+ .addAllAudioPids(audioPids)
+ .setAudioTrackIndex(audioPids.isEmpty() ? -1 : 0)
+ .addAllAudioStreamTypes(audioStreamTypes)
+ .setPcrPid(pcrPid)
+ .build();
}
private TunerChannel(
- int programNumber, int type, PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
- mProto = new TunerChannelProto();
- mProto.tsid = 0;
- mProto.virtualMajor = 0;
- mProto.virtualMinor = 0;
- if (channel == null) {
- mProto.shortName = "";
- mProto.programNumber = programNumber;
- } else {
- mProto.shortName = channel.getServiceName();
- mProto.programNumber = channel.getServiceId();
- mProto.serviceType = channel.getServiceType();
+ int programNumber,
+ Channel.TunerType type,
+ PsipData.SdtItem channel,
+ List<PsiData.PmtItem> pmtItems) {
+ String shortName = "";
+ Channel.AtscServiceType serviceType =
+ Channel.AtscServiceType.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION;
+ if (channel != null) {
+ shortName = channel.getServiceName();
+ programNumber = channel.getServiceId();
+ Channel.AtscServiceType chanServiceType =
+ Channel.AtscServiceType.forNumber(channel.getServiceType());
+ if (chanServiceType != null) {
+ serviceType = chanServiceType;
+ }
}
- initProto(pmtItems, type);
+ TunerChannelProto tunerChannelProto =
+ TunerChannelProto.newBuilder()
+ .setShortName(shortName)
+ .setProgramNumber(programNumber)
+ .setServiceType(serviceType)
+ .build();
+ initProto(pmtItems, type, tunerChannelProto);
}
/** Initialize tuner channel with VCT items and PMT items. */
@@ -233,23 +266,23 @@
}
public String getName() {
- return (!mProto.shortName.isEmpty()) ? mProto.shortName : mProto.longName;
+ return !mProto.getShortName().isEmpty() ? mProto.getShortName() : mProto.getLongName();
}
public String getShortName() {
- return mProto.shortName;
+ return mProto.getShortName();
}
public int getProgramNumber() {
- return mProto.programNumber;
+ return mProto.getProgramNumber();
}
public int getServiceType() {
- return mProto.serviceType;
+ return mProto.getServiceType().getNumber();
}
public String getServiceTypeName() {
- int serviceType = mProto.serviceType;
+ int serviceType = getServiceType();
if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) {
return ATSC_SERVICE_TYPE_NAMES[serviceType];
}
@@ -257,105 +290,129 @@
}
public int getVirtualMajor() {
- return mProto.virtualMajor;
+ return mProto.getVirtualMajor();
}
public int getVirtualMinor() {
- return mProto.virtualMinor;
+ return mProto.getVirtualMinor();
+ }
+
+ public DeliverySystemType getDeliverySystemType() {
+ return mProto.getDeliverySystemType();
}
public int getFrequency() {
- return mProto.frequency;
+ return mProto.getFrequency();
}
public String getModulation() {
- return mProto.modulation;
+ return mProto.getModulation();
}
public int getTsid() {
- return mProto.tsid;
+ return mProto.getTsid();
}
public int getVideoPid() {
- return mProto.videoPid;
+ return mProto.getVideoPid();
}
public synchronized void setVideoPid(int videoPid) {
- mProto.videoPid = videoPid;
+ mProto = mProto.toBuilder().setVideoPid(videoPid).build();
}
public int getVideoStreamType() {
- return mProto.videoStreamType;
+ return mProto.getVideoStreamType().getNumber();
}
public int getAudioPid() {
- if (mProto.audioTrackIndex == -1) {
+ if (!mProto.hasAudioTrackIndex() || mProto.getAudioTrackIndex() == -1) {
return INVALID_PID;
}
- return mProto.audioPids[mProto.audioTrackIndex];
+ return mProto.getAudioPids(mProto.getAudioTrackIndex());
}
public int getAudioStreamType() {
- if (mProto.audioTrackIndex == -1) {
+ if (!mProto.hasAudioTrackIndex() || mProto.getAudioTrackIndex() == -1) {
return INVALID_STREAMTYPE;
}
- return mProto.audioStreamTypes[mProto.audioTrackIndex];
+ return mProto.getAudioStreamTypes(mProto.getAudioTrackIndex()).getNumber();
}
public List<Integer> getAudioPids() {
- return Ints.asList(mProto.audioPids);
+ return mProto.getAudioPidsList();
}
public synchronized void setAudioPids(List<Integer> audioPids) {
- mProto.audioPids = Ints.toArray(audioPids);
+ mProto = mProto.toBuilder().clearAudioPids().addAllAudioPids(audioPids).build();
}
public List<Integer> getAudioStreamTypes() {
- return Ints.asList(mProto.audioStreamTypes);
+ List<Channel.AudioStreamType> audioStreamTypes = mProto.getAudioStreamTypesList();
+ List<Integer> audioStreamTypesValues = new ArrayList<>(audioStreamTypes.size());
+
+ for (Channel.AudioStreamType audioStreamType : audioStreamTypes) {
+ audioStreamTypesValues.add(audioStreamType.getNumber());
+ }
+ return audioStreamTypesValues;
}
- public synchronized void setAudioStreamTypes(List<Integer> audioStreamTypes) {
- mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
+ public synchronized void setAudioStreamTypes(List<Integer> audioStreamTypesValues) {
+ List<Channel.AudioStreamType> audioStreamTypes =
+ new ArrayList<>(audioStreamTypesValues.size());
+
+ for (Integer audioStreamTypesValue : audioStreamTypesValues) {
+ audioStreamTypes.add(Channel.AudioStreamType.forNumber(audioStreamTypesValue));
+ }
+ mProto =
+ mProto.toBuilder()
+ .clearAudioStreamTypes()
+ .addAllAudioStreamTypes(audioStreamTypes)
+ .build();
}
public int getPcrPid() {
- return mProto.pcrPid;
+ return mProto.getPcrPid();
}
- public int getType() {
- return mProto.type;
+ public Channel.TunerType getType() {
+ return mProto.getType();
}
public synchronized void setFilepath(String filepath) {
- mProto.filepath = filepath == null ? "" : filepath;
+ mProto = mProto.toBuilder().setFilepath(filepath == null ? "" : filepath).build();
}
public String getFilepath() {
- return mProto.filepath;
+ return mProto.getFilepath();
}
public synchronized void setVirtualMajor(int virtualMajor) {
- mProto.virtualMajor = virtualMajor;
+ mProto = mProto.toBuilder().setVirtualMajor(virtualMajor).build();
}
public synchronized void setVirtualMinor(int virtualMinor) {
- mProto.virtualMinor = virtualMinor;
+ mProto = mProto.toBuilder().setVirtualMinor(virtualMinor).build();
}
public synchronized void setShortName(String shortName) {
- mProto.shortName = shortName == null ? "" : shortName;
+ mProto = mProto.toBuilder().setShortName(shortName == null ? "" : shortName).build();
+ }
+
+ public synchronized void setDeliverySystemType(DeliverySystemType deliverySystemType) {
+ mProto = mProto.toBuilder().setDeliverySystemType(deliverySystemType).build();
}
public synchronized void setFrequency(int frequency) {
- mProto.frequency = frequency;
+ mProto = mProto.toBuilder().setFrequency(frequency).build();
}
public synchronized void setModulation(String modulation) {
- mProto.modulation = modulation == null ? "" : modulation;
+ mProto = mProto.toBuilder().setModulation(modulation == null ? "" : modulation).build();
}
public boolean hasVideo() {
- return mProto.videoPid != INVALID_PID;
+ return mProto.hasVideoPid() && mProto.getVideoPid() != INVALID_PID;
}
public boolean hasAudio() {
@@ -363,11 +420,11 @@
}
public long getChannelId() {
- return mProto.channelId;
+ return mProto.getChannelId();
}
public synchronized void setChannelId(long channelId) {
- mProto.channelId = channelId;
+ mProto = mProto.toBuilder().setChannelId(channelId).build();
}
/**
@@ -379,11 +436,11 @@
* href="https://developer.android.com/reference/android/media/tv/TvContract.Channels.html#COLUMN_LOCKED">link</a>
*/
public boolean isLocked() {
- return mProto.locked;
+ return mProto.getLocked();
}
public synchronized void setLocked(boolean locked) {
- mProto.locked = locked;
+ mProto = mProto.toBuilder().setLocked(locked).build();
}
public String getDisplayNumber() {
@@ -391,92 +448,91 @@
}
public String getDisplayNumber(boolean ignoreZeroMinorNumber) {
- if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) {
+ if (getVirtualMajor() != 0 && (getVirtualMinor() != 0 || !ignoreZeroMinorNumber)) {
return String.format(
- "%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR, mProto.virtualMinor);
- } else if (mProto.virtualMajor != 0) {
- return Integer.toString(mProto.virtualMajor);
+ "%d%c%d", getVirtualMajor(), CHANNEL_NUMBER_SEPARATOR, getVirtualMinor());
+ } else if (getVirtualMajor() != 0) {
+ return Integer.toString(getVirtualMajor());
} else {
- return Integer.toString(mProto.programNumber);
+ return Integer.toString(getProgramNumber());
}
}
public String getDescription() {
- return mProto.description;
+ return mProto.getDescription();
}
@Override
public synchronized void setHasCaptionTrack() {
- mProto.hasCaptionTrack = true;
+ mProto = mProto.toBuilder().setHasCaptionTrack(true).build();
}
@Override
public boolean hasCaptionTrack() {
- return mProto.hasCaptionTrack;
+ return mProto.getHasCaptionTrack();
}
@Override
public List<AtscAudioTrack> getAudioTracks() {
- return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks));
+ return mProto.getAudioTracksList();
}
public synchronized void setAudioTracks(List<AtscAudioTrack> audioTracks) {
- mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]);
+ mProto = mProto.toBuilder().clearAudioTracks().addAllAudioTracks(audioTracks).build();
}
@Override
public List<AtscCaptionTrack> getCaptionTracks() {
- return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks));
+ return mProto.getCaptionTracksList();
}
public synchronized void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
- mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]);
+ mProto = mProto.toBuilder().clearCaptionTracks().addAllCaptionTracks(captionTracks).build();
}
public synchronized void selectAudioTrack(int index) {
- if (0 <= index && index < mProto.audioPids.length) {
- mProto.audioTrackIndex = index;
- } else {
- mProto.audioTrackIndex = -1;
+ if (index < 0 || index >= mProto.getAudioPidsCount()) {
+ index = -1;
}
+ mProto = mProto.toBuilder().setAudioTrackIndex(index).build();
}
public synchronized void setRecordingProhibited(boolean recordingProhibited) {
- mProto.recordingProhibited = recordingProhibited;
+ mProto = mProto.toBuilder().setRecordingProhibited(recordingProhibited).build();
}
public boolean isRecordingProhibited() {
- return mProto.recordingProhibited;
+ return mProto.getRecordingProhibited();
}
public synchronized void setVideoFormat(String videoFormat) {
- mProto.videoFormat = videoFormat == null ? "" : videoFormat;
+ mProto = mProto.toBuilder().setVideoFormat(videoFormat == null ? "" : videoFormat).build();
}
public String getVideoFormat() {
- return mProto.videoFormat;
+ return mProto.getVideoFormat();
}
@Override
public String toString() {
- switch (mProto.type) {
- case Channel.TunerType.TYPE_FILE:
+ switch (getType()) {
+ case TYPE_FILE:
return String.format(
"{%d-%d %s} Filepath: %s, ProgramNumber %d",
- mProto.virtualMajor,
- mProto.virtualMinor,
- mProto.shortName,
- mProto.filepath,
- mProto.programNumber);
+ getVirtualMajor(),
+ getVirtualMinor(),
+ getShortName(),
+ getFilepath(),
+ getProgramNumber());
// case Channel.TunerType.TYPE_TUNER:
default:
return String.format(
"{%d-%d %s} Frequency: %d, ProgramNumber %d",
- mProto.virtualMajor,
- mProto.virtualMinor,
- mProto.shortName,
- mProto.frequency,
- mProto.programNumber);
+ getVirtualMajor(),
+ getVirtualMinor(),
+ getShortName(),
+ getFrequency(),
+ getProgramNumber());
}
}
@@ -495,6 +551,9 @@
if (ret != 0) {
return ret;
}
+ if (getDeliverySystemType() != channel.getDeliverySystemType()) {
+ return 1;
+ }
// For FileTsStreamer, file paths should be compared.
return StringUtils.compare(getFilepath(), channel.getFilepath());
}
@@ -509,20 +568,21 @@
@Override
public int hashCode() {
- return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath());
+ return Objects.hash(getDeliverySystemType(), getFrequency(), getProgramNumber(), getName(),
+ getFilepath());
}
// Serialization
public synchronized byte[] toByteArray() {
try {
- return MessageNano.toByteArray(mProto);
+ return mProto.toByteArray();
} catch (Exception e) {
// Retry toByteArray. b/34197766
Log.w(
TAG,
"TunerChannel or its variables are modified in multiple thread without lock",
e);
- return MessageNano.toByteArray(mProto);
+ return mProto.toByteArray();
}
}
diff --git a/tuner/src/com/android/tv/tuner/DvbDeviceAccessor.java b/tuner/src/com/android/tv/tuner/dvb/DvbDeviceAccessor.java
similarity index 99%
rename from tuner/src/com/android/tv/tuner/DvbDeviceAccessor.java
rename to tuner/src/com/android/tv/tuner/dvb/DvbDeviceAccessor.java
index 217433d..8be27c9 100644
--- a/tuner/src/com/android/tv/tuner/DvbDeviceAccessor.java
+++ b/tuner/src/com/android/tv/tuner/dvb/DvbDeviceAccessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.tuner;
+package com.android.tv.tuner.dvb;
import android.content.Context;
import android.media.tv.TvInputManager;
diff --git a/tuner/src/com/android/tv/tuner/DvbTunerHal.java b/tuner/src/com/android/tv/tuner/dvb/DvbTunerHal.java
similarity index 97%
rename from tuner/src/com/android/tv/tuner/DvbTunerHal.java
rename to tuner/src/com/android/tv/tuner/dvb/DvbTunerHal.java
index c802ebb..7f68e37 100644
--- a/tuner/src/com/android/tv/tuner/DvbTunerHal.java
+++ b/tuner/src/com/android/tv/tuner/dvb/DvbTunerHal.java
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.tv.tuner;
+package com.android.tv.tuner.dvb;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.android.tv.common.compat.TvInputConstantCompat;
-import com.android.tv.tuner.DvbDeviceAccessor.DvbDeviceInfoWrapper;
+import com.android.tv.tuner.TunerHal;
+import com.android.tv.tuner.dvb.DvbDeviceAccessor.DvbDeviceInfoWrapper;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
diff --git a/tuner/src/com/android/tv/tuner/builtin/BuiltInTunerHalFactory.java b/tuner/src/com/android/tv/tuner/dvb/DvbTunerHalFactory.java
similarity index 60%
rename from tuner/src/com/android/tv/tuner/builtin/BuiltInTunerHalFactory.java
rename to tuner/src/com/android/tv/tuner/dvb/DvbTunerHalFactory.java
index 9a0be74..24d7e1f 100644
--- a/tuner/src/com/android/tv/tuner/builtin/BuiltInTunerHalFactory.java
+++ b/tuner/src/com/android/tv/tuner/dvb/DvbTunerHalFactory.java
@@ -14,39 +14,29 @@
* limitations under the License.
*/
-package com.android.tv.tuner.builtin;
+package com.android.tv.tuner.dvb;
import android.content.Context;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.Pair;
-import com.android.tv.common.customization.CustomizationManager;
-import com.android.tv.common.feature.Model;
-import com.android.tv.tuner.DvbTunerHal;
+
import com.android.tv.tuner.api.Tuner;
import com.android.tv.tuner.api.TunerFactory;
-
/** TunerHal factory that creates all built in tuner types. */
-public final class BuiltInTunerHalFactory implements TunerFactory {
- private static final String TAG = "BuiltInTunerHalFactory";
+public final class DvbTunerHalFactory implements TunerFactory {
+ private static final String TAG = "DvbTunerHalFactory";
private static final boolean DEBUG = false;
- private Integer mBuiltInTunerType;
+ private final int mBuiltInTunerType = Tuner.BUILT_IN_TUNER_TYPE_LINUX_DVB;
- public static final TunerFactory INSTANCE = new BuiltInTunerHalFactory();
+ public static final TunerFactory INSTANCE = new DvbTunerHalFactory();
- private BuiltInTunerHalFactory() {}
+ private DvbTunerHalFactory() {}
@Tuner.BuiltInTunerType
private int getBuiltInTunerType(Context context) {
- if (mBuiltInTunerType == null) {
- mBuiltInTunerType = 0;
- if (CustomizationManager.hasLinuxDvbBuiltInTuner(context)
- && DvbTunerHal.getNumberOfDevices(context) > 0) {
- mBuiltInTunerType = Tuner.BUILT_IN_TUNER_TYPE_LINUX_DVB;
- }
- }
return mBuiltInTunerType;
}
@@ -80,17 +70,6 @@
@Override
@WorkerThread
public Pair<Integer, Integer> getTunerTypeAndCount(Context context) {
- if (useBuiltInTuner(context)) {
- if (getBuiltInTunerType(context) == Tuner.BUILT_IN_TUNER_TYPE_LINUX_DVB) {
- return new Pair<>(
- Tuner.TUNER_TYPE_BUILT_IN, DvbTunerHal.getNumberOfDevices(context));
- }
- } else {
- int usbTunerCount = DvbTunerHal.getNumberOfDevices(context);
- if (usbTunerCount > 0) {
- return new Pair<>(Tuner.TUNER_TYPE_USB, usbTunerCount);
- }
- }
- return new Pair<>(null, 0);
+ return Pair.create(Tuner.TUNER_TYPE_BUILT_IN, DvbTunerHal.getNumberOfDevices(context));
}
}
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java b/tuner/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
index e48cb03..124e04d 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
@@ -23,9 +23,9 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
-import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Pair;
+
import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
@@ -34,24 +34,20 @@
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
-import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
-import com.google.android.exoplayer2.source.ExtractorMediaSource.EventListener;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
-import com.google.android.exoplayer2.upstream.DataSpec;
+import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
-import com.google.android.exoplayer2.upstream.TransferListener;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -72,7 +68,6 @@
private final long mId;
private final Handler.Callback mSourceReaderWorker;
- private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
private BufferManager.SampleBuffer mSampleBuffer;
private Handler mSourceReaderHandler;
@@ -91,11 +86,10 @@
public ExoPlayerSampleExtractor(
Uri uri,
- final DataSource source,
+ DataSource source,
BufferManager bufferManager,
PlaybackBufferListener bufferListener,
- boolean isRecording,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlagsoncurrentDvrPlaybackFlags) {
+ boolean isRecording) {
this(
uri,
source,
@@ -103,8 +97,7 @@
bufferListener,
isRecording,
Looper.myLooper(),
- new HandlerThread("SourceReaderThread"),
- concurrentDvrPlaybackFlagsoncurrentDvrPlaybackFlags);
+ new HandlerThread("SourceReaderThread"));
}
@VisibleForTesting
@@ -116,88 +109,25 @@
PlaybackBufferListener bufferListener,
boolean isRecording,
Looper workerLooper,
- HandlerThread sourceReaderThread,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags) {
+ HandlerThread sourceReaderThread) {
// It'll be used as a timeshift file chunk name's prefix.
mId = System.currentTimeMillis();
- mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
-
- EventListener eventListener =
- new EventListener() {
- @Override
- public void onLoadError(IOException error) {
- mError = error;
- }
- };
mSourceReaderThread = sourceReaderThread;
mSourceReaderWorker =
new SourceReaderWorker(
new ExtractorMediaSource(
uri,
- new com.google.android.exoplayer2.upstream.DataSource.Factory() {
- @Override
- public com.google.android.exoplayer2.upstream.DataSource
- createDataSource() {
- // Returns an adapter implementation for ExoPlayer V2
- // DataSource interface.
- return new com.google.android.exoplayer2.upstream
- .DataSource() {
-
- private @Nullable Uri uri;
-
- // TODO: uncomment once this is part of the public API.
- // @Override
- public void addTransferListener(
- TransferListener transferListener) {
- // Do nothing. Unsupported in V1.
- }
-
- @Override
- public long open(DataSpec dataSpec) throws IOException {
- this.uri = dataSpec.uri;
- return source.open(
- new com.google.android.exoplayer.upstream
- .DataSpec(
- dataSpec.uri,
- dataSpec.postBody,
- dataSpec.absoluteStreamPosition,
- dataSpec.position,
- dataSpec.length,
- dataSpec.key,
- dataSpec.flags));
- }
-
- @Override
- public int read(
- byte[] buffer, int offset, int readLength)
- throws IOException {
- return source.read(buffer, offset, readLength);
- }
-
- @Override
- public @Nullable Uri getUri() {
- return uri;
- }
-
- @Override
- public void close() throws IOException {
- source.close();
- uri = null;
- }
- };
- }
- },
+ /* dataSourceFactory= */ () -> source,
new ExoPlayerExtractorsFactory(),
new Handler(workerLooper),
- eventListener));
+ /* eventListener= */ error -> mError = error));
if (isRecording) {
mSampleBuffer =
new RecordingSampleBuffer(
bufferManager,
bufferListener,
false,
- mConcurrentDvrPlaybackFlags,
RecordingSampleBuffer.BUFFER_REASON_RECORDING);
} else {
if (bufferManager == null) {
@@ -208,7 +138,6 @@
bufferManager,
bufferListener,
true,
- mConcurrentDvrPlaybackFlags,
RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK);
}
}
@@ -240,15 +169,11 @@
public SourceReaderWorker(MediaSource sampleSource) {
mSampleSource = sampleSource;
mSampleSourceListener =
- new MediaSource.SourceInfoRefreshListener() {
- @Override
- public void onSourceInfoRefreshed(
- MediaSource source, Timeline timeline, Object manifest) {
- // Dynamic stream change is not supported yet. b/28169263
- // For now, this will cause EOS and playback reset.
- }
+ (source, timeline, manifest) -> {
+ // Dynamic stream change is not supported yet. b/28169263
+ // For now, this will cause EOS and playback reset.
};
- mSampleSource.prepareSource(null, false, mSampleSourceListener, null);
+ mSampleSource.prepareSource(mSampleSourceListener, null);
mDecoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
@@ -365,9 +290,8 @@
mMediaPeriod =
mSampleSource.createPeriod(
new MediaSource.MediaPeriodId(0),
- new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)
-// AOSP_Comment_Out , 0
- );
+ new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
+ 0);
mMediaPeriod.prepare(this, 0);
try {
mMediaPeriod.maybeThrowPrepareError();
@@ -486,7 +410,7 @@
sample.data.position(0);
sample.data.put(mDecoderInputBuffer.data);
sample.data.flip();
- mPendingSamples.add(new Pair<>(index, sample));
+ mPendingSamples.add(Pair.create(index, sample));
return;
}
mVideoTrackMet = true;
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java b/tuner/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
index 9749e4b..d6685b2 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
@@ -17,14 +17,16 @@
package com.android.tv.tuner.exoplayer;
import android.os.Handler;
+
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
+
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.MediaFormatUtil;
import com.google.android.exoplayer.SampleHolder;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -44,15 +46,10 @@
private final BufferManager mBufferManager;
private final PlaybackBufferListener mBufferListener;
private BufferManager.SampleBuffer mSampleBuffer;
- private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
- public FileSampleExtractor(
- BufferManager bufferManager,
- PlaybackBufferListener bufferListener,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags) {
+ public FileSampleExtractor(BufferManager bufferManager, PlaybackBufferListener bufferListener) {
mBufferManager = bufferManager;
mBufferListener = bufferListener;
- mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
mTrackCount = -1;
}
@@ -80,7 +77,6 @@
mBufferManager,
mBufferListener,
true,
- mConcurrentDvrPlaybackFlags,
RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK);
mSampleBuffer.init(ids, mTrackFormats);
return true;
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
index 6781c61..67cf992 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
@@ -43,7 +43,8 @@
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioTrack;
-import com.google.android.exoplayer.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DataSource;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -147,7 +148,7 @@
*
* @param rendererBuilder the builder of track renderers
* @param handler the handler for the playback events in track renderers
- * @param sourceManager the manager for {@link DataSource}
+ * @param sourceManager the manager for {@link TsDataSource}
* @param capabilities the {@link AudioCapabilities} of the current device
* @param listener the listener for playback state changes
*/
@@ -214,7 +215,7 @@
}
/**
- * Creates renderers and {@link DataSource} and initializes player.
+ * Creates renderers and {@link TsDataSource} and initializes player.
*
* @param context a {@link Context} instance
* @param channel to play
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java b/tuner/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
index e043907..a807adb 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
@@ -17,6 +17,7 @@
package com.android.tv.tuner.exoplayer;
import android.content.Context;
+
import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilder;
import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilderCallback;
import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
@@ -25,25 +26,19 @@
import com.google.android.exoplayer.MediaCodecSelector;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import com.google.android.exoplayer2.upstream.DataSource;
/** Builder for renderer objects for {@link MpegTsPlayer}. */
public class MpegTsRendererBuilder implements RendererBuilder {
private final Context mContext;
private final BufferManager mBufferManager;
private final PlaybackBufferListener mBufferListener;
- private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
public MpegTsRendererBuilder(
- Context context,
- BufferManager bufferManager,
- PlaybackBufferListener bufferListener,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags) {
+ Context context, BufferManager bufferManager, PlaybackBufferListener bufferListener) {
mContext = context;
mBufferManager = bufferManager;
mBufferListener = bufferListener;
- mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
}
@Override
@@ -52,13 +47,8 @@
// Build the video and audio renderers.
SampleExtractor extractor =
dataSource == null
- ? new MpegTsSampleExtractor(
- mBufferManager, mBufferListener, mConcurrentDvrPlaybackFlags)
- : new MpegTsSampleExtractor(
- dataSource,
- mBufferManager,
- mBufferListener,
- mConcurrentDvrPlaybackFlags);
+ ? new MpegTsSampleExtractor(mBufferManager, mBufferListener)
+ : new MpegTsSampleExtractor(dataSource, mBufferManager, mBufferListener);
SampleSource sampleSource = new MpegTsSampleSource(extractor);
MpegTsVideoTrackRenderer videoRenderer =
new MpegTsVideoTrackRenderer(
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java b/tuner/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java
index 582f18c..0b4f980 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java
@@ -18,6 +18,7 @@
import android.net.Uri;
import android.os.Handler;
+
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
import com.android.tv.tuner.exoplayer.buffer.SamplePool;
@@ -25,9 +26,9 @@
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import com.google.android.exoplayer2.upstream.DataSource;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -59,27 +60,19 @@
}
/**
- * Creates MpegTsSampleExtractor for {@link DataSource}.
+ * Creates MpegTsSampleExtractor for a {@link DataSource}.
*
* @param source the {@link DataSource} to extract from
* @param bufferManager the manager for reading & writing samples backed by physical storage
* @param bufferListener the {@link PlaybackBufferListener} to notify buffer storage status
- * @param concurrentDvrPlaybackFlags
*/
public MpegTsSampleExtractor(
DataSource source,
BufferManager bufferManager,
- PlaybackBufferListener bufferListener,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags) {
-
+ PlaybackBufferListener bufferListener) {
mSampleExtractor =
new ExoPlayerSampleExtractor(
- Uri.EMPTY,
- source,
- bufferManager,
- bufferListener,
- false,
- concurrentDvrPlaybackFlags);
+ Uri.EMPTY, source, bufferManager, bufferListener, false);
init();
}
@@ -91,11 +84,8 @@
* change
*/
public MpegTsSampleExtractor(
- BufferManager bufferManager,
- PlaybackBufferListener bufferListener,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags) {
- mSampleExtractor =
- new FileSampleExtractor(bufferManager, bufferListener, concurrentDvrPlaybackFlags);
+ BufferManager bufferManager, PlaybackBufferListener bufferListener) {
+ mSampleExtractor = new FileSampleExtractor(bufferManager, bufferListener);
init();
}
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java b/tuner/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java
index bab74c9..fb88e5b 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java
@@ -246,6 +246,7 @@
mSource.seekToUs(positionUs);
AUDIO_TRACK.reset();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ // b/21824483 workaround
// resetSessionId() will create a new framework AudioTrack instead of reusing old one.
AUDIO_TRACK.resetSessionId();
}
@@ -284,6 +285,7 @@
// Ensure playback stops, after EoS was notified.
// Sometimes MediaCodecTrackRenderer does not fetch EoS timely
// after EoS was notified here long before.
+ // see b/21909113
long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs;
if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) {
throw new ExoPlaybackException("Much time has elapsed after EoS");
@@ -592,6 +594,7 @@
}
mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
} else {
+ // TODO: Remove this workaround when b/22023809 is resolved.
if (mPreviousPositionUs
> audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) {
Log.e(
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/tuner/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
index c32540c..b8d8523 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
@@ -23,10 +23,13 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.tuner.exoplayer.SampleExtractor;
+
import com.google.android.exoplayer.SampleHolder;
+
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -400,13 +403,13 @@
SampleChunk sampleChunk =
mSampleChunkCreator.createSampleChunk(
samplePool, file, positionUs, mChunkCallback);
- map.put(positionUs, new Pair(sampleChunk, 0));
+ map.put(positionUs, Pair.create(sampleChunk, 0));
if (updateIndexFile) {
mStorageManager.updateIndexFile(id, map.size(), positionUs, sampleChunk, 0);
}
return sampleChunk;
} else {
- map.put(positionUs, new Pair(currentChunk, currentOffset));
+ map.put(positionUs, Pair.create(currentChunk, currentOffset));
if (updateIndexFile) {
mStorageManager.updateIndexFile(
id, map.size(), positionUs, currentChunk, currentOffset);
@@ -447,7 +450,7 @@
chunk);
basePositionUs = position.basePositionUs;
}
- map.put(position.positionUs, new Pair(chunk, position.offset));
+ map.put(position.positionUs, Pair.create(chunk, position.offset));
}
}
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java b/tuner/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
index f19756e..0e1cbe9 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
@@ -19,8 +19,7 @@
import android.media.MediaFormat;
import android.util.Log;
import android.util.Pair;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.google.protobuf.nano.MessageNano;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -369,7 +368,7 @@
META_FILE_TYPE_CAPTION + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX));
File file = new File(getBufferDir(), fileName);
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
- out.write(MessageNano.toByteArray(track));
+ track.writeTo(out);
} catch (Exception e) {
Log.e(TAG, "Fail to write caption info to files", e);
}
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java b/tuner/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
index d95642c..00a3430 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
@@ -20,14 +20,16 @@
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.util.Log;
+
import com.android.tv.tuner.exoplayer.MpegTsPlayer;
import com.android.tv.tuner.exoplayer.SampleExtractor;
+
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.util.Assertions;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -69,7 +71,6 @@
private final BufferManager mBufferManager;
private final PlaybackBufferListener mBufferListener;
private final @BufferReason int mBufferReason;
- private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
private int mTrackCount;
private boolean[] mTrackSelected;
@@ -104,18 +105,15 @@
* @param bufferManager the manager of {@link SampleChunk}
* @param bufferListener the listener for buffer I/O event
* @param enableTrickplay {@code true} when trickplay should be enabled
- * @param concurrentDvrPlaybackFlags
* @param bufferReason the reason for caching samples {@link BufferReason}
*/
public RecordingSampleBuffer(
BufferManager bufferManager,
PlaybackBufferListener bufferListener,
boolean enableTrickplay,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
@BufferReason int bufferReason) {
mBufferManager = bufferManager;
mBufferListener = bufferListener;
- mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
if (bufferListener != null) {
bufferListener.onBufferStateChanged(enableTrickplay);
}
@@ -133,13 +131,7 @@
mReadSampleQueues = new ArrayList<>();
mSampleChunkIoHelper =
new SampleChunkIoHelper(
- ids,
- mediaFormats,
- mBufferReason,
- mBufferManager,
- mSamplePool,
- mIoCallback,
- mConcurrentDvrPlaybackFlags);
+ ids, mediaFormats, mBufferReason, mBufferManager, mSamplePool, mIoCallback);
for (int i = 0; i < mTrackCount; ++i) {
mReadSampleQueues.add(i, new SampleQueue(mSamplePool));
}
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java b/tuner/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
index f4d3bf8..d16d080 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
@@ -24,12 +24,14 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
+
import com.android.tv.common.SoftPreconditions;
import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer.BufferReason;
+
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -64,7 +66,6 @@
private final BufferManager mBufferManager;
private final SamplePool mSamplePool;
private final IoCallback mIoCallback;
- private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
private Handler mIoHandler;
private final ConcurrentLinkedQueue<SampleHolder> mReadSampleBuffers[];
@@ -121,7 +122,6 @@
* @param bufferManager manager of {@link SampleChunk} collections
* @param samplePool allocator for a sample
* @param ioCallback listeners for I/O events
- * @param concurrentDvrPlaybackFlags
*/
public SampleChunkIoHelper(
List<String> ids,
@@ -129,8 +129,7 @@
@BufferReason int bufferReason,
BufferManager bufferManager,
SamplePool samplePool,
- IoCallback ioCallback,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags) {
+ IoCallback ioCallback) {
mTrackCount = ids.size();
mIds = ids;
mMediaFormats = mediaFormats;
@@ -138,7 +137,6 @@
mBufferManager = bufferManager;
mSamplePool = samplePool;
mIoCallback = ioCallback;
- mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount];
mHandlerReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount];
@@ -184,9 +182,7 @@
}
try {
- if (mConcurrentDvrPlaybackFlags.enabled()
- && mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING
- && mTrackCount > 0) {
+ if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING && mTrackCount > 0) {
// Saves meta information for recording.
List<BufferManager.TrackFormat> audios = new ArrayList<>(mTrackCount);
List<BufferManager.TrackFormat> videos = new ArrayList<>(mTrackCount);
@@ -384,8 +380,7 @@
private void doOpenWrite(int index) throws IOException {
boolean updateIndexFile =
- mConcurrentDvrPlaybackFlags.enabled()
- && (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING)
+ (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING)
&& (MimeTypes.isVideo(mMediaFormats.get(index).mimeType)
|| MimeTypes.isAudio(mMediaFormats.get(index).mimeType));
@@ -426,13 +421,10 @@
SampleHolder sample = mReadIoStates[index].read();
if (sample != null) {
mHandlerReadSampleBuffers[index].offer(sample);
- if (mConcurrentDvrPlaybackFlags.enabled()) {
- mReadChunkOffset[index] = mReadIoStates[index].getOffset();
- mReadChunkPositionUs[index] = sample.timeUs;
- }
+ mReadChunkOffset[index] = mReadIoStates[index].getOffset();
+ mReadChunkPositionUs[index] = sample.timeUs;
} else {
- if (mConcurrentDvrPlaybackFlags.enabled()
- && mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK) {
+ if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK) {
// Update Index, to load new Samples
updateIndex(index, mReadChunkOffset[index]);
}
@@ -485,9 +477,7 @@
: mWriteIoStates[params.index].getChunk();
int currentOffset = (int) mWriteIoStates[params.index].getOffset();
boolean updateIndexFile =
- mConcurrentDvrPlaybackFlags.enabled()
- && (mBufferReason
- == RecordingSampleBuffer.BUFFER_REASON_RECORDING)
+ (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING)
&& (MimeTypes.isVideo(mMediaFormats.get(index).mimeType)
|| MimeTypes.isAudio(
mMediaFormats.get(index).mimeType));
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/VideoRendererExoV2.java b/tuner/src/com/android/tv/tuner/exoplayer2/VideoRendererExoV2.java
index 1203900..a71352f 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer2/VideoRendererExoV2.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/VideoRendererExoV2.java
@@ -24,7 +24,6 @@
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
-import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.lang.reflect.Field;
@@ -48,8 +47,8 @@
private static final String SOFTWARE_DECODER_NAME_PREFIX = "OMX.google.";
private static final long ALLOWED_JOINING_TIME_MS = 5000;
private static final int DROPPED_FRAMES_NOTIFICATION_THRESHOLD = 10;
- private static final int MIN_HD_HEIGHT = 720;
- private static Field sRenderedFirstFrameField;
+ // private static final int MIN_HD_HEIGHT = 720;
+ private static Field sRenderedFirstFrameField;
private final boolean mIsSwCodecEnabled;
private boolean mCodecIsSwPreferred;
@@ -108,16 +107,18 @@
return decoderInfos;
}
- @Override
- protected void onInputFormatChanged(Format format) throws ExoPlaybackException {
- mCodecIsSwPreferred =
- MimeTypes.VIDEO_MPEG2.equals(format.sampleMimeType)
- && format.height < MIN_HD_HEIGHT;
- super.onInputFormatChanged(format);
- }
+ // TODO: Uncomment once ExoPlayer v2.10.0 is released [Internal ref: b/130625979].
+ // @Override
+ // protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
+ // Format format = formatHolder.format;
+ // mCodecIsSwPreferred =
+ // MimeTypes.VIDEO_MPEG2.equals(format.sampleMimeType)
+ // && format.height < MIN_HD_HEIGHT;
+ // super.onInputFormatChanged(format);
+ // }
- @Override
- protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
+ @Override
+ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
super.onPositionReset(positionUs, joining);
// Disabling pre-rendering of the first frame in order to avoid a frozen picture when
// starting the playback. We do this only once, when the renderer is enabled at first, since
diff --git a/tuner/src/com/android/tv/tuner/features/TunerFeatures.java b/tuner/src/com/android/tv/tuner/features/TunerFeatures.java
index 6033a3a..6ee5aa8 100644
--- a/tuner/src/com/android/tv/tuner/features/TunerFeatures.java
+++ b/tuner/src/com/android/tv/tuner/features/TunerFeatures.java
@@ -19,9 +19,9 @@
import static com.android.tv.common.feature.FeatureUtils.OFF;
import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.feature.DeveloperPreferenceFeature;
import com.android.tv.common.feature.Feature;
import com.android.tv.common.feature.Model;
-import com.android.tv.common.feature.PropertyFeature;
import com.android.tv.common.feature.Sdk;
/**
@@ -39,10 +39,11 @@
* <p>Prefer software based codec for SD channels.
*/
public static final Feature USE_SW_CODEC_FOR_SD =
- PropertyFeature.create(
+ DeveloperPreferenceFeature.create(
"use_sw_codec_for_sd",
- false
- );
+ // On Nexus Player, SW codec is better than HW codec in terms of picture
+ // quality.
+ Model.NEXUS_PLAYER.isEnabled());
/**
* Does the TvProvider on the installed device allow systems inserts to the programs table.
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunChannelScan.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunChannelScan.java
new file mode 100644
index 0000000..38610dd
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunChannelScan.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2016 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.tuner.hdhomerun;
+
+import android.content.Context;
+import android.media.tv.TvContract;
+import android.os.ConditionVariable;
+import android.util.Log;
+import android.util.Xml;
+import com.android.tv.tuner.api.ChannelScanListener;
+import com.android.tv.tuner.data.TunerChannel;
+import com.android.tv.tuner.ts.EventDetector.EventListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.regex.Pattern;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/** A helper class to perform channel scan on HDHomeRun tuner. */
+public class HdHomeRunChannelScan {
+ private static final String TAG = "HdHomeRunChannelScan";
+ private static final boolean DEBUG = false;
+
+ private static final String LINEUP_FILENAME = "lineup.xml";
+ private static final String NAME_LINEUP = "Lineup";
+ private static final String NAME_PROGRAM = "Program";
+ private static final String NAME_GUIDE_NUMBER = "GuideNumber";
+ private static final String NAME_GUIDE_NAME = "GuideName";
+ private static final String NAME_HD = "HD";
+ private static final String NAME_TAGS = "Tags";
+ private static final String NAME_DRM = "DRM";
+
+ private final Context mContext;
+ private final ChannelScanListener mEventListener;
+ private final HdHomeRunTunerHal mTunerHal;
+ private int mProgramCount;
+
+ public HdHomeRunChannelScan(
+ Context context, EventListener eventListener, HdHomeRunTunerHal hal) {
+ mContext = context;
+ mEventListener = eventListener;
+ mTunerHal = hal;
+ }
+
+ public void scan(ConditionVariable conditionStopped) {
+ String urlString = "http://" + mTunerHal.getIpAddress() + "/" + LINEUP_FILENAME;
+ if (DEBUG) Log.d(TAG, "Reading " + urlString);
+ URL url;
+ HttpURLConnection connection = null;
+ InputStream inputStream;
+ try {
+ url = new URL(urlString);
+ connection = (HttpURLConnection) url.openConnection();
+ connection.setReadTimeout(HdHomeRunTunerHal.READ_TIMEOUT_MS_FOR_URLCONNECTION);
+ connection.setConnectTimeout(HdHomeRunTunerHal.CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION);
+ connection.setRequestMethod("GET");
+ connection.setDoInput(true);
+ connection.connect();
+ inputStream = connection.getInputStream();
+ } catch (IOException e) {
+ Log.e(TAG, "Connection failed: " + urlString, e);
+ if (connection != null) {
+ connection.disconnect();
+ }
+ return;
+ }
+ if (conditionStopped.block(-1)) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // Does nothing.
+ }
+ connection.disconnect();
+ return;
+ }
+
+ XmlPullParser parser = Xml.newPullParser();
+ try {
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
+ parser.setInput(inputStream, null);
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, NAME_LINEUP);
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (conditionStopped.block(-1)) {
+ break;
+ }
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+ // Starts by looking for the program tag
+ if (name.equals(NAME_PROGRAM)) {
+ readProgram(parser);
+ } else {
+ skip(parser);
+ }
+ }
+ inputStream.close();
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Parse error", e);
+ }
+ connection.disconnect();
+ mTunerHal.markAsScannedDevice(mContext);
+ }
+
+ private void readProgram(XmlPullParser parser) throws XmlPullParserException, IOException {
+ parser.require(XmlPullParser.START_TAG, null, NAME_PROGRAM);
+ String guideNumber = "";
+ String guideName = "";
+ String videoFormat = null;
+ String tags = "";
+ boolean recordingProhibited = false;
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+ if (name.equals(NAME_GUIDE_NUMBER)) {
+ guideNumber = readText(parser, NAME_GUIDE_NUMBER);
+ } else if (name.equals(NAME_GUIDE_NAME)) {
+ guideName = readText(parser, NAME_GUIDE_NAME);
+ } else if (name.equals(NAME_HD)) {
+ videoFormat = TvContract.Channels.VIDEO_FORMAT_720P;
+ skip(parser);
+ } else if (name.equals(NAME_TAGS)) {
+ tags = readText(parser, NAME_TAGS);
+ } else if (name.equals(NAME_DRM)) {
+ String drm = readText(parser, NAME_DRM);
+ try {
+ recordingProhibited = (Integer.parseInt(drm)) != 0;
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Load DRM property failed: illegal number: " + drm);
+ // If DRM property is present, we treat it as copy-once or copy-never.
+ recordingProhibited = true;
+ }
+ } else {
+ skip(parser);
+ }
+ }
+ if (!tags.isEmpty()) {
+ // Skip encrypted channels since we don't know how to decrypt them.
+ return;
+ }
+ int major;
+ int minor = 0;
+ final String separator = Character.toString(HdHomeRunTunerHal.VCHANNEL_SEPARATOR);
+ if (guideNumber.contains(separator)) {
+ String[] parts = guideNumber.split(Pattern.quote(separator));
+ major = Integer.parseInt(parts[0]);
+ minor = Integer.parseInt(parts[1]);
+ } else {
+ major = Integer.parseInt(guideNumber);
+ }
+ // Need to assign a unique program number (i.e. mProgramCount) to avoid being duplicated.
+ mEventListener.onChannelDetected(
+ TunerChannel.forNetwork(
+ major, minor, mProgramCount++, guideName, recordingProhibited, videoFormat),
+ true);
+ }
+
+ private String readText(XmlPullParser parser, String name)
+ throws IOException, XmlPullParserException {
+ String result = "";
+ parser.require(XmlPullParser.START_TAG, null, name);
+ if (parser.next() == XmlPullParser.TEXT) {
+ result = parser.getText();
+ parser.nextTag();
+ }
+ parser.require(XmlPullParser.END_TAG, null, name);
+ if (DEBUG) Log.d(TAG, "<" + name + ">=" + result);
+ return result;
+ }
+
+ private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException();
+ }
+ int depth = 1;
+ while (depth != 0) {
+ switch (parser.next()) {
+ case XmlPullParser.END_TAG:
+ depth--;
+ break;
+ case XmlPullParser.START_TAG:
+ depth++;
+ break;
+ }
+ }
+ }
+}
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunControlSocket.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunControlSocket.java
new file mode 100644
index 0000000..ce7c518
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunControlSocket.java
@@ -0,0 +1,226 @@
+/*
+ * 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.tuner.hdhomerun;
+
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.util.Pair;
+import com.android.tv.tuner.hdhomerun.HdHomeRunDiscover.HdHomeRunDiscoverDevice;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class to send/receive control commands and results to/from HDHomeRun devices via TCP sockets.
+ * {@link #close()} method should be called after usage to close the TCP socket.
+ */
+class HdHomeRunControlSocket implements AutoCloseable {
+ private static final String TAG = "HdHomeRunControlSocket";
+ private static final boolean DEBUG = false;
+
+ private int mDesiredDeviceId;
+ private int mDesiredDeviceIp;
+ private int mActualDeviceId;
+ private int mActualDeviceIp;
+ private Socket mSocket;
+
+ HdHomeRunControlSocket(int deviceId, int deviceIp) {
+ mDesiredDeviceId = deviceId;
+ mDesiredDeviceIp = deviceIp;
+ mActualDeviceId = 0;
+ mActualDeviceIp = 0;
+ }
+
+ /**
+ * Gets control settings from HDHomeRun devices.
+ *
+ * @param name the name of the field whose value we want to get.
+ */
+ @Nullable
+ String get(String name) {
+ byte[] data = new byte[name.length() + 3];
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.put(HdHomeRunUtils.HDHOMERUN_TAG_GETSET_NAME);
+ buffer.put((byte) (name.length() + 1));
+ buffer.put(name.getBytes());
+
+ // Send & Receive.
+ byte[] result =
+ sendAndReceive(
+ data,
+ HdHomeRunUtils.HDHOMERUN_TYPE_GETSET_REQUEST,
+ HdHomeRunUtils.HDHOMERUN_CONTROL_RECEIVE_TIMEOUT_MS);
+ if (result == null) {
+ if (DEBUG) Log.d(TAG, "Cannot get result for " + name);
+ return null;
+ }
+
+ // Response.
+ buffer = ByteBuffer.wrap(result);
+ while (true) {
+ Pair<Byte, byte[]> tagAndValue = HdHomeRunUtils.readTaggedValue(buffer);
+ if (tagAndValue == null) {
+ break;
+ }
+ switch (tagAndValue.first) {
+ case HdHomeRunUtils.HDHOMERUN_TAG_GETSET_VALUE:
+ // Removes the 0 tail.
+ return new String(
+ Arrays.copyOfRange(
+ tagAndValue.second, 0, tagAndValue.second.length - 1));
+ case HdHomeRunUtils.HDHOMERUN_TAG_ERROR_MESSAGE:
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /** Gets ID of HDHomeRun devices. */
+ int getDeviceId() {
+ if (!connectAndUpdateDeviceInfo()) {
+ return 0;
+ }
+ return mActualDeviceId;
+ }
+
+ private boolean connectAndUpdateDeviceInfo() {
+ if (mSocket != null) {
+ return true;
+ }
+ if ((mDesiredDeviceId == 0) && (mDesiredDeviceIp == 0)) {
+ if (DEBUG) Log.d(TAG, "Desired ID and IP cannot be both zero.");
+ return false;
+ }
+ if (HdHomeRunUtils.isIpMulticast(mDesiredDeviceIp)) {
+ if (DEBUG) Log.d(TAG, "IP cannot be multicast IP.");
+ return false;
+ }
+
+ // Find device.
+ List<HdHomeRunDiscoverDevice> result =
+ HdHomeRunUtils.findHdHomeRunDevices(
+ mDesiredDeviceIp,
+ HdHomeRunUtils.HDHOMERUN_DEVICE_TYPE_WILDCARD,
+ mDesiredDeviceId,
+ 1);
+ if (result.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Cannot find device on: " + mDesiredDeviceIp);
+ return false;
+ }
+ mActualDeviceIp = result.get(0).mIpAddress;
+ mActualDeviceId = result.get(0).mDeviceId;
+
+ // Create socket and initiate connection.
+ mSocket = new Socket();
+ try {
+ mSocket.connect(
+ new InetSocketAddress(
+ HdHomeRunUtils.intToAddress(mActualDeviceIp),
+ HdHomeRunUtils.HDHOMERUN_CONTROL_TCP_PORT),
+ HdHomeRunUtils.HDHOMERUN_CONTROL_CONNECT_TIMEOUT_MS);
+ } catch (IOException e) {
+ if (DEBUG) Log.d(TAG, "Cannot connect to socket: " + mSocket);
+ mSocket = null;
+ return false;
+ }
+
+ // Success.
+ Log.i(TAG, "Connected to socket: " + mSocket);
+ return true;
+ }
+
+ private byte[] sendAndReceive(byte[] data, short type, int timeout) {
+ byte[] sealedData = HdHomeRunUtils.sealFrame(data, type);
+ for (int i = 0; i < 2; i++) {
+ if (mSocket == null && !connectAndUpdateDeviceInfo()) {
+ return null;
+ }
+ if (!send(sealedData)) {
+ continue;
+ }
+ Pair<Short, byte[]> receivedData = receive(timeout);
+ if (receivedData == null || receivedData.first == null) {
+ continue;
+ }
+ if (receivedData.first != type + 1) {
+ if (DEBUG) Log.d(TAG, "Returned type incorrect: " + receivedData.first);
+ close();
+ continue;
+ }
+ return receivedData.second;
+ }
+ return null;
+ }
+
+ private boolean send(byte[] data) {
+ try {
+ OutputStream out = mSocket.getOutputStream();
+ mSocket.setSoTimeout(HdHomeRunUtils.HDHOMERUN_CONTROL_SEND_TIMEOUT_MS);
+ out.write(data);
+ } catch (IOException e) {
+ if (DEBUG) Log.d(TAG, "Cannot send packet to socket: " + mSocket);
+ close();
+ return false;
+ }
+ return true;
+ }
+
+ private Pair<Short, byte[]> receive(int timeout) {
+ byte[] receivedData = new byte[3074];
+ try {
+ InputStream input = mSocket.getInputStream();
+ mSocket.setSoTimeout(timeout);
+ int index = 0;
+ long startTime = System.currentTimeMillis();
+ while (System.currentTimeMillis() - startTime < timeout) {
+ int length = receivedData.length - index;
+ index += input.read(receivedData, index, length);
+ Pair<Short, byte[]> result = HdHomeRunUtils.openFrame(receivedData, index);
+ if (result != null) {
+ if (result.first == HdHomeRunUtils.HDHOMERUN_TYPE_INVALID) {
+ if (DEBUG) Log.d(TAG, "Returned type is invalid.");
+ close();
+ return null;
+ }
+ return result;
+ }
+ if (DEBUG) Log.d(TAG, "Received result is null!");
+ }
+ } catch (IOException e) {
+ if (DEBUG) Log.d(TAG, "Cannot receive from socket: " + mSocket);
+ close();
+ }
+ return null;
+ }
+
+ @Override
+ public void close() {
+ if (mSocket != null) {
+ try {
+ mSocket.close();
+ } catch (IOException e) {
+ // Do nothing
+ }
+ mSocket = null;
+ }
+ }
+}
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunDevice.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunDevice.java
new file mode 100644
index 0000000..dcf87ca
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunDevice.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 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.tuner.hdhomerun;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * An HDHomeRun device detected on the network. This abstraction only contains network data
+ * necessary to establish a connection with the device and does not represent a communication
+ * channel with the device itself. Currently, we only support devices with HTTP streaming
+ * functionality.
+ */
+public class HdHomeRunDevice implements Parcelable {
+ private int mIpAddress;
+ private int mDeviceType;
+ private int mDeviceId;
+ private int mTunerIndex;
+ private String mDeviceModel;
+
+ /**
+ * Creates {@code HdHomeRunDevice} object from a parcel.
+ *
+ * @param parcel The parcel to create {@code HdHomeRunDevice} object from.
+ */
+ public HdHomeRunDevice(Parcel parcel) {
+ mIpAddress = parcel.readInt();
+ mDeviceType = parcel.readInt();
+ mDeviceId = parcel.readInt();
+ mTunerIndex = parcel.readInt();
+ mDeviceModel = parcel.readString();
+ }
+
+ /**
+ * Creates {@code HdHomeRunDevice} object from IP address, device type, device ID and tuner
+ * index.
+ *
+ * @param ipAddress The IP address to create {@code HdHomeRunDevice} object from.
+ * @param deviceType The device type to create {@code HdHomeRunDevice} object from.
+ * @param deviceId The device ID to create {@code HdHomeRunDevice} object from.
+ * @param tunerIndex The tuner index to {@code HdHomeRunDevice} object from.
+ */
+ public HdHomeRunDevice(
+ int ipAddress, int deviceType, int deviceId, int tunerIndex, String deviceModel) {
+ mIpAddress = ipAddress;
+ mDeviceType = deviceType;
+ mDeviceId = deviceId;
+ mTunerIndex = tunerIndex;
+ mDeviceModel = deviceModel;
+ }
+
+ /**
+ * Returns the IP address.
+ *
+ * @return the IP address of this homerun device.
+ */
+ public int getIpAddress() {
+ return mIpAddress;
+ }
+
+ /**
+ * Returns the device type.
+ *
+ * @return the type of device for this homerun device.
+ */
+ public int getDeviceType() {
+ return mDeviceType;
+ }
+
+ /**
+ * Returns the device ID.
+ *
+ * @return the device ID of this homerun device.
+ */
+ public int getDeviceId() {
+ return mDeviceId;
+ }
+
+ /**
+ * Returns the tuner index.
+ *
+ * @return the tuner index of this homerun device.
+ */
+ public int getTunerIndex() {
+ return mTunerIndex;
+ }
+
+ /**
+ * Returns the device model.
+ *
+ * @return the device model of this homerun device.
+ */
+ public String getDeviceModel() {
+ return mDeviceModel;
+ }
+
+ @Override
+ public String toString() {
+ String ipAddress =
+ ""
+ + ((mIpAddress >>> 24) & 0xff)
+ + "."
+ + ((mIpAddress >>> 16) & 0xff)
+ + "."
+ + ((mIpAddress >>> 8) & 0xff)
+ + "."
+ + (mIpAddress & 0xff);
+ return String.format("[%x-%d:%s]", mDeviceId, mTunerIndex, ipAddress);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mIpAddress);
+ out.writeInt(mDeviceType);
+ out.writeInt(mDeviceId);
+ out.writeInt(mTunerIndex);
+ out.writeString(mDeviceModel);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = hash * 31 + getIpAddress();
+ hash = hash * 31 + getDeviceType();
+ hash = hash * 31 + getDeviceId();
+ hash = hash * 31 + getTunerIndex();
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof HdHomeRunDevice)) {
+ return false;
+ }
+ HdHomeRunDevice rhs = (HdHomeRunDevice) o;
+ return rhs != null
+ && getIpAddress() == rhs.getIpAddress()
+ && getDeviceType() == rhs.getDeviceType()
+ && getDeviceId() == rhs.getDeviceId()
+ && getTunerIndex() == rhs.getTunerIndex()
+ && TextUtils.equals(getDeviceModel(), rhs.getDeviceModel());
+ }
+
+ public static final Parcelable.Creator<HdHomeRunDevice> CREATOR =
+ new Parcelable.Creator<HdHomeRunDevice>() {
+
+ @Override
+ public HdHomeRunDevice createFromParcel(Parcel in) {
+ return new HdHomeRunDevice(in);
+ }
+
+ @Override
+ public HdHomeRunDevice[] newArray(int size) {
+ return new HdHomeRunDevice[size];
+ }
+ };
+}
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunDiscover.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunDiscover.java
new file mode 100644
index 0000000..85b3450
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunDiscover.java
@@ -0,0 +1,446 @@
+/*
+ * 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.tuner.hdhomerun;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.util.Pair;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
+/** A class to discover HDHomeRun devices on the network with UDP broadcasting. */
+class HdHomeRunDiscover {
+ private static final String TAG = "HdHomeRunDiscover";
+ private static final boolean DEBUG = false;
+
+ private static final int HDHOMERUN_DISCOVER_MAX_SOCK_COUNT = 16;
+ private static final int HDHOMERUN_DISCOVER_RETRY_LIMIT = 2;
+ private static final int HDHOMERUN_DISCOVER_TIMEOUT_MS = 500;
+ private static final int HDHOMERUN_DISCOVER_RECEIVE_WAITE_TIME_MS = 10;
+
+ private List<HdHomeRunDiscoverSocket> mSockets = new ArrayList<>();
+
+ /** Creates a discover object. If cannot add a default socket, return {@code null}. */
+ static HdHomeRunDiscover create() {
+ HdHomeRunDiscover hdHomeRunDiscover = new HdHomeRunDiscover();
+ // Create a routable socket (always first entry).
+ if (!hdHomeRunDiscover.addSocket(0, 0)) {
+ return null;
+ }
+ return hdHomeRunDiscover;
+ }
+
+ /** Closes and releases all sockets required by this discover object. */
+ void close() {
+ for (HdHomeRunDiscoverSocket discoverSocket : mSockets) {
+ discoverSocket.close();
+ }
+ }
+
+ /** Finds HDHomeRun devices. */
+ @NonNull
+ List<HdHomeRunDiscoverDevice> findDevices(
+ int targetIp, int deviceType, int deviceId, int maxCount) {
+ List<HdHomeRunDiscoverDevice> resultList = new ArrayList<>();
+ resetLocalIpSockets();
+ for (int retry = 0;
+ retry < HDHOMERUN_DISCOVER_RETRY_LIMIT && resultList.isEmpty();
+ retry++) {
+ int localIpSent = send(targetIp, deviceType, deviceId);
+ if (localIpSent == 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Cannot send to target ip: " + HdHomeRunUtils.getIpString(targetIp));
+ }
+ continue;
+ }
+ long timeout = System.currentTimeMillis() + HDHOMERUN_DISCOVER_TIMEOUT_MS * localIpSent;
+ while (System.currentTimeMillis() < timeout) {
+ HdHomeRunDiscoverDevice result = new HdHomeRunDiscoverDevice();
+ if (!receive(result)) {
+ continue;
+ }
+ // Filter.
+ if (deviceType != HdHomeRunUtils.HDHOMERUN_DEVICE_TYPE_WILDCARD
+ && deviceType != result.mDeviceType) {
+ continue;
+ }
+ if (deviceId != HdHomeRunUtils.HDHOMERUN_DEVICE_ID_WILDCARD
+ && deviceId != result.mDeviceId) {
+ continue;
+ }
+ if (isObsoleteDevice(deviceId)) {
+ continue;
+ }
+ // Ensure not already in list.
+ if (resultList.contains(result)) {
+ continue;
+ }
+ // Add to list.
+ resultList.add(result);
+ if (resultList.size() >= maxCount) {
+ break;
+ }
+ }
+ }
+ return resultList;
+ }
+
+ private boolean addSocket(int localIp, int subnetMask) {
+ for (int i = 1; i < mSockets.size(); i++) {
+ HdHomeRunDiscoverSocket discoverSocket = mSockets.get(i);
+ if ((discoverSocket.mLocalIp == localIp)
+ && (discoverSocket.mSubnetMask == subnetMask)) {
+ discoverSocket.mDetected = true;
+ return true;
+ }
+ }
+ if (mSockets.size() >= HDHOMERUN_DISCOVER_MAX_SOCK_COUNT) {
+ return false;
+ }
+ DatagramSocket socket;
+ try {
+ socket = new DatagramSocket(0, HdHomeRunUtils.intToAddress(localIp));
+ socket.setBroadcast(true);
+ } catch (IOException e) {
+ if (DEBUG) Log.d(TAG, "Cannot create socket: " + HdHomeRunUtils.getIpString(localIp));
+ return false;
+ }
+ // Write socket entry.
+ mSockets.add(new HdHomeRunDiscoverSocket(socket, true, localIp, subnetMask));
+ return true;
+ }
+
+ private void resetLocalIpSockets() {
+ for (int i = 1; i < mSockets.size(); i++) {
+ mSockets.get(i).mDetected = false;
+ mSockets.get(i).mDiscoverPacketSent = false;
+ }
+ List<LocalIpInfo> ipInfoList = getLocalIpInfo(HDHOMERUN_DISCOVER_MAX_SOCK_COUNT);
+ for (LocalIpInfo ipInfo : ipInfoList) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Add local IP: "
+ + HdHomeRunUtils.getIpString(ipInfo.mIpAddress)
+ + ", "
+ + HdHomeRunUtils.getIpString(ipInfo.mSubnetMask));
+ }
+ addSocket(ipInfo.mIpAddress, ipInfo.mSubnetMask);
+ }
+ Iterator<HdHomeRunDiscoverSocket> iterator = mSockets.iterator();
+ while (iterator.hasNext()) {
+ HdHomeRunDiscoverSocket discoverSocket = iterator.next();
+ if (!discoverSocket.mDetected) {
+ discoverSocket.close();
+ iterator.remove();
+ }
+ }
+ }
+
+ private List<LocalIpInfo> getLocalIpInfo(int maxCount) {
+ Enumeration<NetworkInterface> interfaces;
+ try {
+ interfaces = NetworkInterface.getNetworkInterfaces();
+ } catch (SocketException e) {
+ return Collections.emptyList();
+ }
+ List<LocalIpInfo> result = new ArrayList<>();
+ while (interfaces.hasMoreElements()) {
+ NetworkInterface networkInterface = interfaces.nextElement();
+ for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
+ InetAddress inetAddress = interfaceAddress.getAddress();
+ if (!inetAddress.isAnyLocalAddress()
+ && !inetAddress.isLinkLocalAddress()
+ && !inetAddress.isLoopbackAddress()
+ && !inetAddress.isMulticastAddress()) {
+ LocalIpInfo localIpInfo = new LocalIpInfo();
+ localIpInfo.mIpAddress = HdHomeRunUtils.addressToInt(inetAddress.getAddress());
+ localIpInfo.mSubnetMask =
+ (0x7fffffff >> (31 - interfaceAddress.getNetworkPrefixLength()));
+ result.add(localIpInfo);
+ if (result.size() >= maxCount) {
+ return result;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private int send(int targetIp, int deviceType, int deviceId) {
+ return targetIp == 0
+ ? sendWildcardIp(deviceType, deviceId)
+ : sendTargetIp(targetIp, deviceType, deviceId);
+ }
+
+ private int sendWildcardIp(int deviceType, int deviceId) {
+ int localIpSent = 0;
+
+ // Send subnet broadcast using each local ip socket.
+ // This will work with multiple separate 169.254.x.x interfaces.
+ for (int i = 1; i < mSockets.size(); i++) {
+ HdHomeRunDiscoverSocket discoverSocket = mSockets.get(i);
+ int targetIp = discoverSocket.mLocalIp | ~discoverSocket.mSubnetMask;
+ if (DEBUG) Log.d(TAG, "Send: " + HdHomeRunUtils.getIpString(targetIp));
+ localIpSent += discoverSocket.send(targetIp, deviceType, deviceId) ? 1 : 0;
+ }
+ // If no local ip sockets then fall back to sending a global broadcast letting
+ // the OS choose the interface.
+ if (localIpSent == 0) {
+ if (DEBUG) Log.d(TAG, "Send: " + HdHomeRunUtils.getIpString(0xFFFFFFFF));
+ localIpSent = mSockets.get(0).send(0xFFFFFFFF, deviceType, deviceId) ? 1 : 0;
+ }
+ return localIpSent;
+ }
+
+ private int sendTargetIp(int targetIp, int deviceType, int deviceId) {
+ int localIpSent = 0;
+
+ // Send targeted packet from any local ip that is in the same subnet.
+ // This will work with multiple separate 169.254.x.x interfaces.
+ for (int i = 1; i < mSockets.size(); i++) {
+ HdHomeRunDiscoverSocket discoverSocket = mSockets.get(i);
+ if (discoverSocket.mSubnetMask == 0) {
+ continue;
+ }
+ if ((targetIp & discoverSocket.mSubnetMask)
+ != (discoverSocket.mLocalIp & discoverSocket.mSubnetMask)) {
+ continue;
+ }
+ localIpSent += discoverSocket.send(targetIp, deviceType, deviceId) ? 1 : 0;
+ }
+ // If target IP does not match a local subnet then fall back to letting the OS choose
+ // the gateway interface.
+ if (localIpSent == 0) {
+ localIpSent = mSockets.get(0).send(targetIp, deviceType, deviceId) ? 1 : 0;
+ }
+ return localIpSent;
+ }
+
+ private boolean receive(HdHomeRunDiscoverDevice result) {
+ for (HdHomeRunDiscoverSocket discoverSocket : mSockets) {
+ if (discoverSocket.mDiscoverPacketSent && discoverSocket.receive(result)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isObsoleteDevice(int deviceId) {
+ switch (deviceId >> 20) {
+ case 0x100: /* TECH-US/TECH3-US */
+ return (deviceId < 0x10040000);
+ case 0x120: /* TECH3-EU */
+ return (deviceId < 0x12030000);
+ case 0x101: /* HDHR-US */
+ case 0x102: /* HDHR-T1-US */
+ case 0x103: /* HDHR3-US */
+ case 0x111: /* HDHR3-DT */
+ case 0x121: /* HDHR-EU */
+ case 0x122: /* HDHR3-EU */
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static class HdHomeRunDiscoverDevice {
+ int mIpAddress;
+ int mDeviceType;
+ int mDeviceId;
+ int mTunerCount;
+ String mBaseUrl;
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other instanceof HdHomeRunDiscoverDevice) {
+ HdHomeRunDiscoverDevice o = (HdHomeRunDiscoverDevice) other;
+ return mIpAddress == o.mIpAddress
+ && mDeviceType == o.mDeviceType
+ && mDeviceId == o.mDeviceId;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mIpAddress;
+ result = 31 * result + mDeviceType;
+ result = 31 * result + mDeviceId;
+ return result;
+ }
+ }
+
+ private static class HdHomeRunDiscoverSocket {
+ DatagramSocket mSocket;
+ boolean mDetected;
+ boolean mDiscoverPacketSent;
+ int mLocalIp;
+ int mSubnetMask;
+
+ private HdHomeRunDiscoverSocket(
+ DatagramSocket socket, boolean detected, int localIp, int subnetMask) {
+ mSocket = socket;
+ mDetected = detected;
+ mLocalIp = localIp;
+ mSubnetMask = subnetMask;
+ }
+
+ private boolean send(int targetIp, int deviceType, int deviceId) {
+ byte[] data = new byte[12];
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.put(HdHomeRunUtils.HDHOMERUN_TAG_DEVICE_TYPE);
+ buffer.put((byte) 4);
+ buffer.putInt(deviceType);
+ buffer.put(HdHomeRunUtils.HDHOMERUN_TAG_DEVICE_ID);
+ buffer.put((byte) 4);
+ buffer.putInt(deviceId);
+ data = HdHomeRunUtils.sealFrame(data, HdHomeRunUtils.HDHOMERUN_TYPE_DISCOVER_REQUEST);
+ try {
+ DatagramPacket packet =
+ new DatagramPacket(
+ data,
+ data.length,
+ HdHomeRunUtils.intToAddress(targetIp),
+ HdHomeRunUtils.HDHOMERUN_DISCOVER_UDP_PORT);
+ mSocket.send(packet);
+ if (DEBUG) {
+ Log.d(TAG, "Discover packet sent to: " + HdHomeRunUtils.getIpString(targetIp));
+ }
+ mDiscoverPacketSent = true;
+ } catch (IOException e) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Cannot send discover packet to socket("
+ + HdHomeRunUtils.getIpString(mLocalIp)
+ + ")");
+ }
+ mDiscoverPacketSent = false;
+ }
+ return mDiscoverPacketSent;
+ }
+
+ private boolean receive(HdHomeRunDiscoverDevice result) {
+ DatagramPacket packet = new DatagramPacket(new byte[3074], 3074);
+ try {
+ mSocket.setSoTimeout(HDHOMERUN_DISCOVER_RECEIVE_WAITE_TIME_MS);
+ mSocket.receive(packet);
+ if (DEBUG) Log.d(TAG, "Received packet, size: " + packet.getLength());
+ } catch (IOException e) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Cannot receive from socket("
+ + HdHomeRunUtils.getIpString(mLocalIp)
+ + ")");
+ }
+ return false;
+ }
+
+ Pair<Short, byte[]> data =
+ HdHomeRunUtils.openFrame(packet.getData(), packet.getLength());
+ if (data == null
+ || data.first == null
+ || data.first != HdHomeRunUtils.HDHOMERUN_TYPE_DISCOVER_REPLY) {
+ if (DEBUG) Log.d(TAG, "Ill-formed packet: " + Arrays.toString(packet.getData()));
+ return false;
+ }
+ result.mIpAddress = HdHomeRunUtils.addressToInt(packet.getAddress().getAddress());
+ if (DEBUG) {
+ Log.d(TAG, "Get Device IP: " + HdHomeRunUtils.getIpString(result.mIpAddress));
+ }
+ ByteBuffer buffer = ByteBuffer.wrap(data.second);
+ while (true) {
+ Pair<Byte, byte[]> tagAndValue = HdHomeRunUtils.readTaggedValue(buffer);
+ if (tagAndValue == null) {
+ break;
+ }
+ switch (tagAndValue.first) {
+ case HdHomeRunUtils.HDHOMERUN_TAG_DEVICE_TYPE:
+ if (tagAndValue.second.length != 4) {
+ break;
+ }
+ result.mDeviceType = ByteBuffer.wrap(tagAndValue.second).getInt();
+ if (DEBUG) Log.d(TAG, "Get Device Type: " + result.mDeviceType);
+ break;
+ case HdHomeRunUtils.HDHOMERUN_TAG_DEVICE_ID:
+ if (tagAndValue.second.length != 4) {
+ break;
+ }
+ result.mDeviceId = ByteBuffer.wrap(tagAndValue.second).getInt();
+ if (DEBUG) Log.d(TAG, "Get Device ID: " + result.mDeviceId);
+ break;
+ case HdHomeRunUtils.HDHOMERUN_TAG_TUNER_COUNT:
+ if (tagAndValue.second.length != 1) {
+ break;
+ }
+ result.mTunerCount = tagAndValue.second[0];
+ if (DEBUG) Log.d(TAG, "Get Tuner Count: " + result.mTunerCount);
+ break;
+ case HdHomeRunUtils.HDHOMERUN_TAG_BASE_URL:
+ result.mBaseUrl = new String(tagAndValue.second);
+ if (DEBUG) Log.d(TAG, "Get Base URL: " + result.mBaseUrl);
+ break;
+ default:
+ break;
+ }
+ }
+ // Fixup for old firmware.
+ if (result.mTunerCount == 0) {
+ switch (result.mDeviceId >> 20) {
+ case 0x102:
+ result.mTunerCount = 1;
+ break;
+ case 0x100:
+ case 0x101:
+ case 0x121:
+ result.mTunerCount = 2;
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+ }
+
+ private void close() {
+ if (mSocket != null) {
+ mSocket.close();
+ }
+ }
+ }
+
+ private static class LocalIpInfo {
+ int mIpAddress;
+ int mSubnetMask;
+ }
+}
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunInterface.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunInterface.java
new file mode 100644
index 0000000..2928aba
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunInterface.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016 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.tuner.hdhomerun;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+import com.android.tv.tuner.hdhomerun.HdHomeRunDiscover.HdHomeRunDiscoverDevice;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** An interface class provides methods to access physical HDHomeRun devices. */
+@WorkerThread
+public class HdHomeRunInterface {
+ private static final String TAG = "HdHomeRunInterface";
+ private static final boolean DEBUG = false;
+
+ private static final int FETCH_DEVICE_NAME_TRY_NUM = 2;
+ private static final int MAX_DEVICES = 1;
+ private static final boolean DISABLE_CABLE = false;
+
+ /**
+ * Scans for HDHomeRun devices on the network.
+ *
+ * @param deviceId The target device ID we want to find, scans for all available devices if
+ * {@code null} or the given ID cannot be found.
+ * @return A set of HDHomeRun devices
+ */
+ @NonNull
+ public static Set<HdHomeRunDevice> scanDevices(Integer deviceId) {
+ List<HdHomeRunDiscoverDevice> discoveredDevices = null;
+ if (deviceId != null) {
+ discoveredDevices =
+ HdHomeRunUtils.findHdHomeRunDevices(
+ 0, HdHomeRunUtils.HDHOMERUN_DEVICE_TYPE_TUNER, deviceId, 1);
+ if (discoveredDevices.isEmpty()) {
+ Log.i(TAG, "Can't find device with ID: " + deviceId);
+ }
+ }
+ if (discoveredDevices == null || discoveredDevices.isEmpty()) {
+ discoveredDevices =
+ HdHomeRunUtils.findHdHomeRunDevices(
+ 0,
+ HdHomeRunUtils.HDHOMERUN_DEVICE_TYPE_TUNER,
+ HdHomeRunUtils.HDHOMERUN_DEVICE_ID_WILDCARD,
+ MAX_DEVICES);
+ if (DEBUG) Log.d(TAG, "Found " + discoveredDevices.size() + " devices");
+ }
+ Set<HdHomeRunDevice> result = new HashSet<>();
+ for (HdHomeRunDiscoverDevice discoveredDevice : discoveredDevices) {
+ String model =
+ fetchDeviceModel(discoveredDevice.mDeviceId, discoveredDevice.mIpAddress);
+ if (model == null) {
+ Log.e(TAG, "Fetching device model failed: " + discoveredDevice.mDeviceId);
+ continue;
+ } else if (DEBUG) {
+ Log.d(TAG, "Fetch Device Model: " + model);
+ }
+ if (DISABLE_CABLE) {
+ if (model != null && model.contains("cablecard")) {
+ // filter out CableCARD devices
+ continue;
+ }
+ }
+ for (int i = 0; i < discoveredDevice.mTunerCount; i++) {
+ result.add(
+ new HdHomeRunDevice(
+ discoveredDevice.mIpAddress,
+ discoveredDevice.mDeviceType,
+ discoveredDevice.mDeviceId,
+ i,
+ model));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns {@code true} if the given device IP, ID and tuner index is available for use.
+ *
+ * @param deviceId The target device ID, 0 denotes a wildcard match.
+ * @param deviceIp The target device IP.
+ * @param tunerIndex The target tuner index of the target device. This parameter is only
+ * meaningful when the target device has multiple tuners.
+ */
+ public static boolean isDeviceAvailable(int deviceId, int deviceIp, int tunerIndex) {
+ // TODO: check the lock state for the given tuner.
+ if ((deviceId == 0) && (deviceIp == 0)) {
+ return false;
+ }
+ if (HdHomeRunUtils.isIpMulticast(deviceIp)) {
+ return false;
+ }
+ if ((deviceId == 0) || (deviceId == HdHomeRunUtils.HDHOMERUN_DEVICE_ID_WILDCARD)) {
+ try (HdHomeRunControlSocket controlSock =
+ new HdHomeRunControlSocket(deviceId, deviceIp)) {
+ deviceId = controlSock.getDeviceId();
+ }
+ }
+ return deviceId != 0;
+ }
+
+ @Nullable
+ private static String fetchDeviceModel(int deviceId, int deviceIp) {
+ for (int i = 0; i < FETCH_DEVICE_NAME_TRY_NUM; i++) {
+ try (HdHomeRunControlSocket controlSock =
+ new HdHomeRunControlSocket(deviceId, deviceIp)) {
+ String model = controlSock.get("/sys/model");
+ if (model != null) {
+ return model;
+ }
+ }
+ }
+ return null;
+ }
+
+ private HdHomeRunInterface() {}
+}
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerHal.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerHal.java
new file mode 100644
index 0000000..8168299
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerHal.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2016 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.tuner.hdhomerun;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.compat.TvInputConstantCompat;
+import com.android.tv.tuner.api.Tuner;
+import com.android.tv.tuner.data.TunerChannel;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+/** Tuner implementation for HdHomeRun */
+public class HdHomeRunTunerHal implements Tuner {
+ private static final String TAG = "HdHomeRunTunerHal";
+ private static final boolean DEBUG = false;
+
+ private static final String CABLECARD_MODEL = "cablecard";
+ private static final String ATSC_MODEL = "atsc";
+ private static final String DVBC_MODEL = "dvbc";
+ private static final String DVBT_MODEL = "dvbt";
+
+ private final HdHomeRunTunerManager mTunerManager;
+ private HdHomeRunDevice mDevice;
+ private BufferedInputStream mInputStream;
+ private HttpURLConnection mConnection;
+ private String mHttpConnectionAddress;
+ private final Context mContext;
+
+ @DeliverySystemType private int mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED;
+
+ public static final char VCHANNEL_SEPARATOR = '.';
+ public static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec
+ public static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec
+
+ public HdHomeRunTunerHal(Context context) {
+ mTunerManager = HdHomeRunTunerManager.getInstance();
+ mContext = context;
+ }
+
+ @Override
+ public boolean openFirstAvailable() {
+ SoftPreconditions.checkState(mDevice == null);
+ try {
+ mDevice = mTunerManager.acquireDevice(mContext);
+ if (mDevice != null) {
+ if (mDeliverySystemType == DELIVERY_SYSTEM_UNDEFINED) {
+ mDeliverySystemType = nativeGetDeliverySystemType(getDeviceId());
+ }
+ }
+ return mDevice != null;
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to open first available device", e);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isDeviceOpen() {
+ return mDevice != null;
+ }
+
+ @Override
+ public boolean isReusable() {
+ return false;
+ }
+
+ @Override
+ public long getDeviceId() {
+ return mDevice == null ? 0 : mDevice.getDeviceId();
+ }
+
+ @Override
+ public void close() throws Exception {
+ closeInputStreamAndDisconnect();
+ if (mDevice != null) {
+ mTunerManager.releaseDevice(mDevice);
+ mDevice = null;
+ }
+ }
+
+ @Override
+ public synchronized boolean tune(
+ int frequency, @ModulationType String modulation, String channelNumber) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "tune(frequency="
+ + frequency
+ + ", modulation="
+ + modulation
+ + ", channelNumber="
+ + channelNumber
+ + ")");
+ }
+ closeInputStreamAndDisconnect();
+ if (TextUtils.isEmpty(channelNumber)) {
+ return false;
+ }
+ channelNumber =
+ channelNumber.replace(TunerChannel.CHANNEL_NUMBER_SEPARATOR, VCHANNEL_SEPARATOR);
+ mHttpConnectionAddress = "http://" + getIpAddress() + ":5004/auto/v" + channelNumber;
+ return connectAndOpenInputStream();
+ }
+
+ private boolean connectAndOpenInputStream() {
+ URL url;
+ try {
+ url = new URL(mHttpConnectionAddress);
+ } catch (MalformedURLException e) {
+ Log.e(TAG, "Invalid address: " + mHttpConnectionAddress, e);
+ return false;
+ }
+ URLConnection connection;
+ try {
+ connection = url.openConnection();
+ connection.setConnectTimeout(CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION);
+ connection.setReadTimeout(READ_TIMEOUT_MS_FOR_URLCONNECTION);
+ if (connection instanceof HttpURLConnection) {
+ mConnection = (HttpURLConnection) connection;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Connection failed: " + mHttpConnectionAddress, e);
+ return false;
+ }
+ try {
+ mInputStream = new BufferedInputStream(connection.getInputStream());
+ } catch (IOException e) {
+ closeInputStreamAndDisconnect();
+ Log.e(TAG, "Failed to get input stream from " + mHttpConnectionAddress, e);
+ return false;
+ }
+ if (DEBUG) Log.d(TAG, "tuning to " + mHttpConnectionAddress);
+ return true;
+ }
+
+ @Override
+ public synchronized boolean addPidFilter(int pid, @FilterType int filterType) {
+ // no-op
+ return true;
+ }
+
+ @Override
+ public synchronized void stopTune() {
+ closeInputStreamAndDisconnect();
+ }
+
+ @Override
+ public synchronized int readTsStream(byte[] javaBuffer, int javaBufferSize) {
+ if (mInputStream != null) {
+ try {
+ // Note: this call sometimes take more than 500ms, because the data is
+ // streamed through network unlike connected tuner devices.
+ return mInputStream.read(javaBuffer, 0, javaBufferSize);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read stream", e);
+ closeInputStreamAndDisconnect();
+ }
+ }
+ if (connectAndOpenInputStream()) {
+ Log.w(TAG, "Tuned by http connection again");
+ } else {
+ Log.e(TAG, "Tuned by http connection again failed");
+ }
+ return 0;
+ }
+
+ @Override
+ public void setHasPendingTune(boolean hasPendingTune) {
+ // no-op
+ }
+
+ protected int nativeGetDeliverySystemType(long deviceId) {
+ String deviceModel = mDevice.getDeviceModel();
+ if (SoftPreconditions.checkState(!TextUtils.isEmpty(deviceModel))) {
+ if (deviceModel.contains(CABLECARD_MODEL) || deviceModel.contains(ATSC_MODEL)) {
+ return DELIVERY_SYSTEM_ATSC;
+ } else if (deviceModel.contains(DVBC_MODEL)) {
+ return DELIVERY_SYSTEM_DVBC;
+ } else if (deviceModel.contains(DVBT_MODEL)) {
+ return DELIVERY_SYSTEM_DVBT;
+ }
+ }
+ return DELIVERY_SYSTEM_UNDEFINED;
+ }
+
+ private void closeInputStreamAndDisconnect() {
+ if (mInputStream != null) {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close input stream", e);
+ }
+ mInputStream = null;
+ }
+ if (mConnection != null) {
+ mConnection.disconnect();
+ mConnection = null;
+ }
+ }
+
+ /** Gets the number of tuners in a given HDHomeRun devices. */
+ public static int getNumberOfDevices() {
+ return HdHomeRunTunerManager.getInstance().getTunerCount();
+ }
+
+ /** Returns the IP address. */
+ public String getIpAddress() {
+ return HdHomeRunUtils.getIpString(mDevice.getIpAddress());
+ }
+
+ /**
+ * Marks the device associated to this instance as a scanned device. Scanned device has higher
+ * priority among multiple HDHomeRun devices.
+ */
+ public void markAsScannedDevice(Context context) {
+ HdHomeRunTunerManager.markAsScannedDevice(context, mDevice);
+ }
+
+ @Override
+ @DeliverySystemType
+ public int getDeliverySystemType() {
+ return Tuner.DELIVERY_SYSTEM_UNDEFINED;
+ }
+
+ @Override
+ public int getSignalStrength() {
+ return TvInputConstantCompat.SIGNAL_STRENGTH_NOT_USED;
+ }
+}
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerHalFactory.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerHalFactory.java
new file mode 100644
index 0000000..6f6b186
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerHalFactory.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 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.tuner.hdhomerun;
+
+import android.content.Context;
+import android.support.annotation.WorkerThread;
+import android.util.Pair;
+
+import com.android.tv.tuner.api.Tuner;
+import com.android.tv.tuner.api.TunerFactory;
+
+/** TunerHal factory that creates all built in tuner types. */
+public final class HdHomeRunTunerHalFactory implements TunerFactory {
+ public static final TunerFactory INSTANCE = new HdHomeRunTunerHalFactory();
+
+ private HdHomeRunTunerHalFactory() {}
+ /**
+ * Creates a TunerHal instance.
+ *
+ * @param context context for creating the TunerHal instance
+ * @return the TunerHal instance
+ */
+ @Override
+ @WorkerThread
+ public synchronized Tuner createInstance(Context context) {
+ Tuner tunerHal = null;
+ if (tunerHal == null) {
+ tunerHal = new HdHomeRunTunerHal(context);
+ }
+ return tunerHal.openFirstAvailable() ? tunerHal : null;
+ }
+
+ /**
+ * Returns if tuner input service would use built-in tuners instead of USB tuners or network
+ * tuners.
+ */
+ @Override
+ public boolean useBuiltInTuner(Context context) {
+ return false;
+ }
+
+ /** Gets the number of tuner devices currently present. */
+ @Override
+ @WorkerThread
+ public Pair<Integer, Integer> getTunerTypeAndCount(Context context) {
+ return Pair.create(Tuner.TUNER_TYPE_NETWORK, HdHomeRunTunerHal.getNumberOfDevices());
+ }
+}
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerManager.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerManager.java
new file mode 100644
index 0000000..9e3ea59
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunTunerManager.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.tuner.hdhomerun;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A class to manage tuner resources of HDHomeRun devices. It handles tuner resource acquisition and
+ * release.
+ */
+class HdHomeRunTunerManager {
+ private static final String TAG = "HdHomeRunTunerManager";
+ private static final boolean DEBUG = false;
+
+ private static final String PREF_KEY_SCANNED_DEVICE_ID = "scanned_device_id";
+
+ private static HdHomeRunTunerManager sInstance;
+
+ private final Set<HdHomeRunDevice> mHdHomeRunDevices = new HashSet<>();
+ private final Set<HdHomeRunDevice> mUsedDevices = new HashSet<>();
+
+ private HdHomeRunTunerManager() {}
+
+ /** Returns the instance of this manager. */
+ public static synchronized HdHomeRunTunerManager getInstance() {
+ if (sInstance == null) {
+ sInstance = new HdHomeRunTunerManager();
+ }
+ return sInstance;
+ }
+
+ /** Returns number of tuners. */
+ @WorkerThread
+ synchronized int getTunerCount() {
+ updateDevicesLocked(null);
+ if (DEBUG) Log.d(TAG, "getTunerCount: " + mHdHomeRunDevices.size());
+ return mHdHomeRunDevices.size();
+ }
+
+ /** Creates an HDHomeRun device. If there is no available one, returns {@code null}. */
+ @WorkerThread
+ synchronized HdHomeRunDevice acquireDevice(Context context) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ int scannedDeviceId = sp.getInt(PREF_KEY_SCANNED_DEVICE_ID, 0);
+ updateDevicesLocked(scannedDeviceId == 0 ? null : scannedDeviceId);
+ if (DEBUG) Log.d(TAG, "createDevice: device count = " + mHdHomeRunDevices.size());
+ HdHomeRunDevice availableDevice = null;
+ // Use the device used for scanning first since other devices might have different line-up.
+ if (scannedDeviceId != 0) {
+ for (HdHomeRunDevice device : mHdHomeRunDevices) {
+ if (!mUsedDevices.contains(device) && scannedDeviceId == device.getDeviceId()) {
+ if (!HdHomeRunInterface.isDeviceAvailable(
+ device.getDeviceId(), device.getIpAddress(), device.getTunerIndex())) {
+ if (DEBUG) Log.d(TAG, "Device not available: " + device);
+ continue;
+ }
+ availableDevice = device;
+ break;
+ }
+ }
+ }
+ if (availableDevice == null) {
+ for (HdHomeRunDevice device : mHdHomeRunDevices) {
+ if (!mUsedDevices.contains(device)) {
+ if (!HdHomeRunInterface.isDeviceAvailable(
+ device.getDeviceId(), device.getIpAddress(), device.getTunerIndex())) {
+ if (DEBUG) Log.d(TAG, "Device not available: " + device);
+ continue;
+ }
+ availableDevice = device;
+ break;
+ }
+ }
+ }
+ if (availableDevice != null) {
+ if (DEBUG) Log.d(TAG, "created device " + availableDevice);
+ mUsedDevices.add(availableDevice);
+ return availableDevice;
+ }
+ return null;
+ }
+
+ /** Releases a created device by {@link #acquireDevice(Context)}. */
+ synchronized void releaseDevice(HdHomeRunDevice device) {
+ if (DEBUG) Log.d(TAG, "releaseDevice: " + device);
+ mUsedDevices.remove(device);
+ }
+
+ /**
+ * Marks the device associated to this instance as a scanned device. Scanned device has higher
+ * priority among multiple HDHomeRun devices.
+ */
+ static void markAsScannedDevice(Context context, HdHomeRunDevice device) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ sp.edit().putInt(PREF_KEY_SCANNED_DEVICE_ID, device.getDeviceId()).apply();
+ }
+
+ private void updateDevicesLocked(Integer deviceId) {
+ mHdHomeRunDevices.clear();
+ mHdHomeRunDevices.addAll(HdHomeRunInterface.scanDevices(deviceId));
+ }
+}
diff --git a/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunUtils.java b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunUtils.java
new file mode 100644
index 0000000..733fc96
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/hdhomerun/HdHomeRunUtils.java
@@ -0,0 +1,194 @@
+/*
+ * 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.tuner.hdhomerun;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.tv.tuner.hdhomerun.HdHomeRunDiscover.HdHomeRunDiscoverDevice;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.zip.CRC32;
+
+class HdHomeRunUtils {
+ private static final String TAG = "HdHomeRunUtils";
+ private static final boolean DEBUG = false;
+
+ static final int HDHOMERUN_DEVICE_TYPE_WILDCARD = 0xFFFFFFFF;
+ static final int HDHOMERUN_DEVICE_TYPE_TUNER = 0x00000001;
+ static final int HDHOMERUN_DEVICE_ID_WILDCARD = 0xFFFFFFFF;
+
+ static final int HDHOMERUN_DISCOVER_UDP_PORT = 65001;
+ static final int HDHOMERUN_CONTROL_TCP_PORT = 65001;
+
+ static final short HDHOMERUN_TYPE_INVALID = -1;
+ static final short HDHOMERUN_TYPE_DISCOVER_REQUEST = 0x0002;
+ static final short HDHOMERUN_TYPE_DISCOVER_REPLY = 0x0003;
+ static final short HDHOMERUN_TYPE_GETSET_REQUEST = 0x0004;
+ static final short HDHOMERUN_TYPE_GETSET_REPLY = 0x0005;
+
+ static final byte HDHOMERUN_TAG_DEVICE_TYPE = 0x01;
+ static final byte HDHOMERUN_TAG_DEVICE_ID = 0x02;
+ static final byte HDHOMERUN_TAG_GETSET_NAME = 0x03;
+ static final int HDHOMERUN_TAG_GETSET_VALUE = 0x04;
+ static final int HDHOMERUN_TAG_ERROR_MESSAGE = 0x05;
+ static final int HDHOMERUN_TAG_TUNER_COUNT = 0x10;
+ static final int HDHOMERUN_TAG_BASE_URL = 0x2A;
+
+ static final int HDHOMERUN_CONTROL_CONNECT_TIMEOUT_MS = 2500;
+ static final int HDHOMERUN_CONTROL_SEND_TIMEOUT_MS = 2500;
+ static final int HDHOMERUN_CONTROL_RECEIVE_TIMEOUT_MS = 2500;
+
+ /**
+ * Finds HDHomeRun devices with given IP, type, and ID.
+ *
+ * @param targetIp {@code 0} to find target devices with broadcasting.
+ * @param deviceType The type of target devices.
+ * @param deviceId The ID of target devices.
+ * @param maxCount Maximum number of devices should be returned.
+ */
+ @NonNull
+ static List<HdHomeRunDiscoverDevice> findHdHomeRunDevices(
+ int targetIp, int deviceType, int deviceId, int maxCount) {
+ if (isIpMulticast(targetIp)) {
+ if (DEBUG) Log.d(TAG, "Target IP cannot be multicast IP.");
+ return Collections.emptyList();
+ }
+ try {
+ HdHomeRunDiscover ds = HdHomeRunDiscover.create();
+ if (ds == null) {
+ if (DEBUG) Log.d(TAG, "Cannot create discover object.");
+ return Collections.emptyList();
+ }
+ List<HdHomeRunDiscoverDevice> result =
+ ds.findDevices(targetIp, deviceType, deviceId, maxCount);
+ ds.close();
+ return result;
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to find HdHomeRun Devices", e);
+ return Collections.emptyList();
+ }
+ }
+
+ /** Returns {@code true} if the given IP is a multi-cast IP. */
+ static boolean isIpMulticast(long ip) {
+ return (ip >= 0xE0000000) && (ip < 0xF0000000);
+ }
+
+ /** Translates a {@code byte[]} address to its integer representation. */
+ static int addressToInt(byte[] address) {
+ return ByteBuffer.wrap(address).order(ByteOrder.LITTLE_ENDIAN).getInt();
+ }
+
+ /** Translates an {@code int} address to a corresponding {@link InetAddress}. */
+ static InetAddress intToAddress(int address) throws UnknownHostException {
+ return InetAddress.getByAddress(
+ ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(address).array());
+ }
+
+ /** Gets {@link String} representation of an {@code int} address. */
+ static String getIpString(int ip) {
+ return String.format(
+ "%d.%d.%d.%d", (ip & 0xff), (ip >> 8 & 0xff), (ip >> 16 & 0xff), (ip >> 24 & 0xff));
+ }
+
+ /**
+ * Opens the packet returned from HDHomeRun devices to acquire the real content and verify it.
+ */
+ static Pair<Short, byte[]> openFrame(byte[] data, int length) {
+ if (length < 4) {
+ return null;
+ }
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ short resultType = buffer.getShort();
+ int dataLength = buffer.getShort() & 0xffff;
+
+ if (dataLength + 8 > length) {
+ // Not finished yet.
+ return null;
+ }
+ byte[] result = new byte[dataLength];
+ buffer.get(result);
+ byte[] calculatedCrc = getCrcFromBytes(Arrays.copyOfRange(data, 0, dataLength + 4));
+ byte[] packetCrc = new byte[4];
+ buffer.get(packetCrc);
+
+ if (!Arrays.equals(calculatedCrc, packetCrc)) {
+ return Pair.create(HDHOMERUN_TYPE_INVALID, null);
+ }
+
+ return Pair.create(resultType, result);
+ }
+
+ /** Seals the contents in a packet to send to HDHomeRun devices. */
+ static byte[] sealFrame(byte[] data, short frameType) {
+ byte[] result = new byte[data.length + 8];
+ ByteBuffer buffer = ByteBuffer.wrap(result);
+ buffer.putShort(frameType);
+ buffer.putShort((short) data.length);
+ buffer.put(data);
+ buffer.put(getCrcFromBytes(Arrays.copyOfRange(result, 0, data.length + 4)));
+ return result;
+ }
+
+ /** Reads a (tag, value) pair from packets returned from HDHomeRun devices. */
+ static Pair<Byte, byte[]> readTaggedValue(ByteBuffer buffer) {
+ try {
+ Byte tag = buffer.get();
+ byte[] value = readVarLength(buffer);
+ return Pair.create(tag, value);
+ } catch (BufferUnderflowException e) {
+ return null;
+ }
+ }
+
+ private static byte[] readVarLength(ByteBuffer buffer) {
+ short length;
+ Byte lengthByte1 = buffer.get();
+ if ((lengthByte1 & 0x80) != 0) {
+ length = buffer.get();
+ length = (short) ((length << 7) + (lengthByte1 & 0x7F));
+ } else {
+ length = lengthByte1;
+ }
+ byte[] result = new byte[length];
+ buffer.get(result);
+ return result;
+ }
+
+ private static byte[] getCrcFromBytes(byte[] data) {
+ CRC32 crc32 = new CRC32();
+ crc32.update(data);
+ long crc = crc32.getValue();
+ byte[] result = new byte[4];
+ for (int offset = 0; offset < 4; offset++) {
+ result[offset] = (byte) (crc & 0xFF);
+ crc >>= 8;
+ }
+ return result;
+ }
+
+ private HdHomeRunUtils() {}
+}
diff --git a/tuner/src/com/android/tv/tuner/modules/TunerModule.java b/tuner/src/com/android/tv/tuner/modules/TunerModule.java
index 4843f38..bf40fbe 100644
--- a/tuner/src/com/android/tv/tuner/modules/TunerModule.java
+++ b/tuner/src/com/android/tv/tuner/modules/TunerModule.java
@@ -16,8 +16,49 @@
package com.android.tv.tuner.modules;
import com.android.tv.tuner.source.TunerSourceModule;
+import com.android.tv.tuner.tvinput.TunerRecordingSessionFactoryImpl;
+import com.android.tv.tuner.tvinput.TunerRecordingSessionWorker;
+import com.android.tv.tuner.tvinput.TunerRecordingSessionWorkerFactory;
+import com.android.tv.tuner.tvinput.TunerSessionExoV2Factory;
+import com.android.tv.tuner.tvinput.TunerSessionV1Factory;
+import com.android.tv.tuner.tvinput.TunerSessionWorker;
+import com.android.tv.tuner.tvinput.TunerSessionWorkerExoV2;
+import com.android.tv.tuner.tvinput.TunerSessionWorkerExoV2Factory;
+import com.android.tv.tuner.tvinput.TunerSessionWorkerFactory;
+import com.android.tv.tuner.tvinput.factory.TunerRecordingSessionFactory;
+import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
+
+import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
+
+import com.android.tv.common.flags.TunerFlags;
/** Dagger module for TV Tuners. */
@Module(includes = {TunerSingletonsModule.class, TunerSourceModule.class})
-public class TunerModule {}
+public abstract class TunerModule {
+
+ @Provides
+ static TunerSessionFactory tunerSessionFactory(
+ TunerFlags tunerFlags,
+ TunerSessionV1Factory tunerSessionFactory,
+ TunerSessionExoV2Factory tunerSessionExoV2Factory) {
+ return tunerFlags.useExoplayerV2() ? tunerSessionExoV2Factory : tunerSessionFactory;
+ }
+
+ @Binds
+ abstract TunerRecordingSessionWorker.Factory tunerRecordingSessionWorkerFactory(
+ TunerRecordingSessionWorkerFactory tunerRecordingSessionWorkerFactory);
+
+ @Binds
+ abstract TunerSessionWorker.Factory tunerSessionWorkerFactory(
+ TunerSessionWorkerFactory tunerSessionWorkerFactory);
+
+ @Binds
+ abstract TunerSessionWorkerExoV2.Factory tunerSessionWorkerExoV2Factory(
+ TunerSessionWorkerExoV2Factory tunerSessionWorkerExoV2Factory);
+
+ @Binds
+ abstract TunerRecordingSessionFactory tunerRecordingSessionFactory(
+ TunerRecordingSessionFactoryImpl impl);
+}
diff --git a/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java b/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java
index 44f689b..0502690 100644
--- a/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java
+++ b/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java
@@ -75,11 +75,14 @@
R.raw.ut_kr_cable_standard_center_frequencies_qam256,
R.raw.ut_kr_all,
R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256,
- R.raw.ut_euro_dvbt_all,
- R.raw.ut_euro_dvbt_all,
R.raw.ut_euro_dvbt_all
+ /* these two resource files are obsolete and removed, so comment them out
+ R.raw.ut_euro_all,
+ R.raw.ut_euro_all */
};
+ protected final String mInputId;
+
protected ScanFragment mLastScanFragment;
protected Integer mTunerType;
protected boolean mNeedToShowPostalCodeFragment;
@@ -90,6 +93,10 @@
private TunerHalCreator mTunerHalCreator;
+ protected BaseTunerSetupActivity(String mInputId) {
+ this.mInputId = mInputId;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG) {
@@ -222,6 +229,7 @@
args1.putInt(
ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, CHANNEL_MAP_SCAN_FILE[actionId]);
args1.putInt(KEY_TUNER_TYPE, mTunerType);
+ args1.putString(ScanFragment.EXTRA_FOR_INPUT_ID, mInputId);
mLastScanFragment.setArguments(args1);
showFragment(mLastScanFragment, true);
return true;
diff --git a/tuner/src/com/android/tv/tuner/setup/ChannelScanFileParser.java b/tuner/src/com/android/tv/tuner/setup/ChannelScanFileParser.java
index 43c584e..2e78270 100644
--- a/tuner/src/com/android/tv/tuner/setup/ChannelScanFileParser.java
+++ b/tuner/src/com/android/tv/tuner/setup/ChannelScanFileParser.java
@@ -56,6 +56,7 @@
}
scanChannelList.add(
ScanChannel.forTuner(
+ tokens[0],
Integer.parseInt(tokens[1]),
tokens[2],
tokens.length == 4 ? Integer.parseInt(tokens[3]) : null));
diff --git a/tuner/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java b/tuner/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
index ebe4e41..db29742 100644
--- a/tuner/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
+++ b/tuner/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
@@ -18,14 +18,12 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import com.android.tv.common.BuildConfig;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.tuner.R;
import java.util.List;
-import java.util.TimeZone;
/** A fragment for connection type selection. */
public class ConnectionTypeFragment extends SetupMultiPaneFragment {
@@ -67,7 +65,6 @@
/** The content fragment of {@link ConnectionTypeFragment}. */
public static class ContentFragment extends SetupGuidedStepFragment {
-
@NonNull
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
diff --git a/tuner/src/com/android/tv/tuner/setup/LineupFragment.java b/tuner/src/com/android/tv/tuner/setup/LineupFragment.java
index 41f755d..224237d 100644
--- a/tuner/src/com/android/tv/tuner/setup/LineupFragment.java
+++ b/tuner/src/com/android/tv/tuner/setup/LineupFragment.java
@@ -20,8 +20,8 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import android.util.Log;
import android.view.View;
import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
diff --git a/tuner/src/com/android/tv/tuner/setup/LiveTvTunerSetupActivity.java b/tuner/src/com/android/tv/tuner/setup/LiveTvTunerSetupActivity.java
deleted file mode 100644
index 741edc7..0000000
--- a/tuner/src/com/android/tv/tuner/setup/LiveTvTunerSetupActivity.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2015 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.tuner.setup;
-
-import android.app.FragmentManager;
-import android.content.pm.PackageManager;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.view.KeyEvent;
-import com.android.tv.common.util.PostalCodeUtils;
-import dagger.android.ContributesAndroidInjector;
-
-/** An activity that serves tuner setup process. */
-public class LiveTvTunerSetupActivity extends BaseTunerSetupActivity {
- private static final String TAG = "LiveTvTunerSetupActivity";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // TODO(shubang): use LocationFragment
- if (checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
- != PackageManager.PERMISSION_GRANTED) {
- // No need to check the request result.
- requestPermissions(
- new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
- PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
- }
- }
-
- @Override
- protected void executeGetTunerTypeAndCountAsyncTask() {
- new AsyncTask<Void, Void, Integer>() {
- @Override
- protected Integer doInBackground(Void... arg0) {
- return mTunerFactory.getTunerTypeAndCount(LiveTvTunerSetupActivity.this).first;
- }
-
- @Override
- protected void onPostExecute(Integer result) {
- if (!LiveTvTunerSetupActivity.this.isDestroyed()) {
- mTunerType = result;
- if (result == null) {
- finish();
- } else if (!mActivityStopped) {
- showInitialFragment();
- } else {
- mPendingShowInitialFragment = true;
- }
- }
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- FragmentManager manager = getFragmentManager();
- int count = manager.getBackStackEntryCount();
- if (count > 0) {
- String lastTag = manager.getBackStackEntryAt(count - 1).getName();
- if (ScanResultFragment.class.getCanonicalName().equals(lastTag) && count >= 2) {
- String secondLastTag = manager.getBackStackEntryAt(count - 2).getName();
- if (ScanFragment.class.getCanonicalName().equals(secondLastTag)) {
- // Pops fragment including ScanFragment.
- manager.popBackStack(
- secondLastTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- return true;
- }
- } else if (ScanFragment.class.getCanonicalName().equals(lastTag)) {
- mLastScanFragment.finishScan(true);
- return true;
- }
- }
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public void onRequestPermissionsResult(
- int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- try {
- // Updating postal code takes time, therefore we should update postal code
- // right after the permission is granted, so that the subsequent operations,
- // especially EPG fetcher, could get the newly updated postal code.
- PostalCodeUtils.updatePostalCode(this);
- } catch (Exception e) {
- // Do nothing
- }
- }
- }
- }
-
- /**
- * Exports {@link LiveTvTunerSetupActivity} for Dagger codegen to create the appropriate
- * injector.
- */
- @dagger.Module
- public abstract static class Module {
- @ContributesAndroidInjector
- abstract LiveTvTunerSetupActivity contributeLiveTvTunerSetupActivityInjector();
- }
-}
diff --git a/tuner/src/com/android/tv/tuner/setup/LocationFragment.java b/tuner/src/com/android/tv/tuner/setup/LocationFragment.java
index 1234ae2..f950405 100644
--- a/tuner/src/com/android/tv/tuner/setup/LocationFragment.java
+++ b/tuner/src/com/android/tv/tuner/setup/LocationFragment.java
@@ -23,16 +23,14 @@
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
import android.util.Log;
-
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import com.android.tv.common.ui.setup.SetupActionHelper;
import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.common.util.LocationUtils;
import com.android.tv.tuner.R;
-
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -40,7 +38,7 @@
/** A fragment shows the rationale of location permission */
public class LocationFragment extends SetupMultiPaneFragment {
private static final String TAG = "com.android.tv.tuner.setup.LocationFragment";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.LocationFragment";
public static final String KEY_POSTAL_CODE = "key_postal_code";
@@ -79,8 +77,7 @@
() -> {
synchronized (mPostalCodeLock) {
if (DEBUG) {
- Log.d(TAG,
- "get location timeout. mPostalCode=" + mPostalCode);
+ Log.d(TAG, "get location timeout. mPostalCode=" + mPostalCode);
}
if (mPostalCode == null) {
// timeout. setup activity will get null postal code
@@ -121,8 +118,7 @@
.id(ACTION_GETTING_LOCATION)
.title(getString(R.string.location_choices_getting_location))
.focusable(false)
- .build()
- );
+ .build());
}
@Override
@@ -147,8 +143,8 @@
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
diff --git a/tuner/src/com/android/tv/tuner/setup/PostalCodeFragment.java b/tuner/src/com/android/tv/tuner/setup/PostalCodeFragment.java
index 5224797..f9ea167 100644
--- a/tuner/src/com/android/tv/tuner/setup/PostalCodeFragment.java
+++ b/tuner/src/com/android/tv/tuner/setup/PostalCodeFragment.java
@@ -18,9 +18,9 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidedActionsStylist;
import android.text.InputFilter;
import android.text.InputFilter.AllCaps;
import android.view.View;
diff --git a/tuner/src/com/android/tv/tuner/setup/ScanFragment.java b/tuner/src/com/android/tv/tuner/setup/ScanFragment.java
index 7d59284..87a79e3 100644
--- a/tuner/src/com/android/tv/tuner/setup/ScanFragment.java
+++ b/tuner/src/com/android/tv/tuner/setup/ScanFragment.java
@@ -40,11 +40,9 @@
import com.android.tv.tuner.R;
import com.android.tv.tuner.api.ScanChannel;
import com.android.tv.tuner.api.Tuner;
+import com.android.tv.tuner.data.Channel.TunerType;
import com.android.tv.tuner.data.PsipData;
import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-
-
import com.android.tv.tuner.prefs.TunerPreferences;
import com.android.tv.tuner.source.FileTsStreamer;
import com.android.tv.tuner.source.TsDataSource;
@@ -74,7 +72,13 @@
public static final int ACTION_FINISH = 2;
public static final String EXTRA_FOR_CHANNEL_SCAN_FILE = "scan_file_choice";
+ public static final String EXTRA_FOR_INPUT_ID = "input_id";
public static final String KEY_CHANNEL_NUMBERS = "channel_numbers";
+
+ // Allows adding audio-only channels (CJ music channel) for which VCT is not present.
+ private static final boolean ADD_CJ_MUSIC_CHANNELS = false;
+ private static final int CJ_MUSIC_CHANNEL_FREQUENCY = 585000000;
+
private static final long CHANNEL_SCAN_SHOW_DELAY_MS = 10000;
private static final long CHANNEL_SCAN_PERIOD_MS = 4000;
private static final long SHOW_PROGRESS_DIALOG_DELAY_MS = 300;
@@ -99,8 +103,6 @@
if (DEBUG) Log.d(TAG, "onCreateView");
View view = super.onCreateView(inflater, container, savedInstanceState);
mChannelNumbers = new ArrayList<>();
- mChannelDataManager = new ChannelDataManager(getActivity().getApplicationContext());
- mChannelDataManager.checkDataVersion(getActivity());
mAdapter = new ChannelAdapter();
mProgressBar = (ProgressBar) view.findViewById(R.id.tune_progress);
mScanningMessage = (TextView) view.findViewById(R.id.tune_description);
@@ -122,8 +124,6 @@
});
Bundle args = getArguments();
int tunerType = (args == null ? 0 : args.getInt(BaseTunerSetupActivity.KEY_TUNER_TYPE, 0));
- // TODO: Handle the case when the fragment is restored.
- startScan(args == null ? 0 : args.getInt(EXTRA_FOR_CHANNEL_SCAN_FILE, 0));
TextView scanTitleView = (TextView) view.findViewById(R.id.tune_title);
switch (tunerType) {
case Tuner.TUNER_TYPE_USB:
@@ -139,6 +139,28 @@
}
@Override
+ public void onStart() {
+ super.onStart();
+ Bundle args = getArguments();
+ String inputId = args == null ? null : args.getString(ScanFragment.EXTRA_FOR_INPUT_ID);
+ if (inputId == null) {
+ Log.w(TAG, "No input ID, stopping setup activity.");
+ getActivity().finish();
+ }
+
+ mChannelDataManager = new ChannelDataManager(getContext().getApplicationContext(), inputId);
+ mChannelDataManager.checkDataVersion(getActivity());
+ }
+
+ @Override
+ public void onStop() {
+ if (mChannelDataManager != null) {
+ mChannelDataManager.release();
+ }
+ super.onStop();
+ }
+
+ @Override
protected int getLayoutResourceId() {
return R.layout.ut_channel_scan;
}
@@ -154,6 +176,13 @@
}
@Override
+ public void onResume() {
+ Bundle args = getArguments();
+ startScan(args == null ? 0 : args.getInt(EXTRA_FOR_CHANNEL_SCAN_FILE, 0));
+ super.onResume();
+ }
+
+ @Override
public void onPause() {
Log.d(TAG, "onPause");
if (mChannelScanTask != null) {
@@ -250,6 +279,7 @@
private final Activity mActivity;
private final int mChannelMapId;
+// AOSP_Comment_Out private final com.android.tv.tuner.hdhomerun.HdHomeRunTunerHal mNetworkTuner;
private final TsStreamer mScanTsStreamer;
private final TsStreamer mFileTsStreamer;
private final ConditionVariable mConditionStopped;
@@ -270,6 +300,13 @@
if (hal == null) {
throw new RuntimeException("Failed to open a DVB device");
}
+ /* Begin_AOSP_Comment_Out
+ if (hal instanceof com.android.tv.tuner.hdhomerun.HdHomeRunTunerHal) {
+ mNetworkTuner = (com.android.tv.tuner.hdhomerun.HdHomeRunTunerHal) hal;
+ } else {
+ mNetworkTuner = null;
+ }
+ End_AOSP_Comment_Out */
mScanTsStreamer = new TunerTsStreamer(hal, this);
}
mFileTsStreamer = SCAN_LOCAL_STREAMS ? new FileTsStreamer(this, mActivity) : null;
@@ -314,6 +351,18 @@
@Override
protected Void doInBackground(Void... params) {
+ /* Begin_AOSP_Comment_Out
+ if (mNetworkTuner != null) {
+ mChannelDataManager.notifyScanStarted();
+ com.android.tv.tuner.hdhomerun.HdHomeRunChannelScan hdHomeRunChannelScan =
+ new com.android.tv.tuner.hdhomerun.HdHomeRunChannelScan(
+ mActivity.getApplicationContext(), this, mNetworkTuner);
+ hdHomeRunChannelScan.scan(mConditionStopped);
+ mChannelDataManager.notifyScanCompleted();
+ publishProgress(MAX_PROGRESS);
+ return null;
+ }
+ End_AOSP_Comment_Out */
mScanChannelList.clear();
if (SCAN_LOCAL_STREAMS) {
FileTsStreamer.addLocalStreamFiles(mScanChannelList);
@@ -376,6 +425,10 @@
e);
}
streamer.stopStream();
+
+ if (ADD_CJ_MUSIC_CHANNELS) {
+ addCjMusicChannel(frequency, modulation);
+ }
addChannelsWithoutVct(scanChannel);
if (System.currentTimeMillis() > startMs + CHANNEL_SCAN_SHOW_DELAY_MS
&& !mChannelListVisible) {
@@ -394,6 +447,24 @@
if (DEBUG) Log.i(TAG, "Channel scan ended");
}
+ private void addCjMusicChannel(int frequency, String modulation) {
+ if (frequency == CJ_MUSIC_CHANNEL_FREQUENCY
+ && mChannelMapId == R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256) {
+ List<TunerChannel> incompleteChannels =
+ mScanTsStreamer instanceof TunerTsStreamer
+ ? ((TunerTsStreamer) mScanTsStreamer).getMalFormedChannels()
+ : new ArrayList<>();
+ for (TunerChannel tunerChannel : incompleteChannels) {
+ if ((tunerChannel.getVideoPid() == TunerChannel.INVALID_PID)
+ && (tunerChannel.getAudioPid() != TunerChannel.INVALID_PID)) {
+ tunerChannel.setFrequency(frequency);
+ tunerChannel.setModulation(modulation);
+ onChannelDetected(tunerChannel, true);
+ }
+ }
+ }
+ }
+
private void addChannelsWithoutVct(ScanChannel scanChannel) {
if (scanChannel.radioFrequencyNumber == null
|| !(mScanTsStreamer instanceof TunerTsStreamer)) {
@@ -403,6 +474,7 @@
((TunerTsStreamer) mScanTsStreamer).getMalFormedChannels()) {
if ((tunerChannel.getVideoPid() != TunerChannel.INVALID_PID)
&& (tunerChannel.getAudioPid() != TunerChannel.INVALID_PID)) {
+ tunerChannel.setDeliverySystemType(scanChannel.deliverySystemType);
tunerChannel.setFrequency(scanChannel.frequency);
tunerChannel.setModulation(scanChannel.modulation);
tunerChannel.setShortName(
@@ -420,9 +492,9 @@
private TsStreamer getStreamer(int type) {
switch (type) {
- case Channel.TunerType.TYPE_TUNER:
+ case TunerType.TYPE_TUNER_VALUE:
return mScanTsStreamer;
- case Channel.TunerType.TYPE_FILE:
+ case TunerType.TYPE_FILE_VALUE:
return mFileTsStreamer;
default:
return null;
diff --git a/tuner/src/com/android/tv/tuner/setup/ScanResultFragment.java b/tuner/src/com/android/tv/tuner/setup/ScanResultFragment.java
index bd3f9ad..01bcc9f 100644
--- a/tuner/src/com/android/tv/tuner/setup/ScanResultFragment.java
+++ b/tuner/src/com/android/tv/tuner/setup/ScanResultFragment.java
@@ -20,8 +20,8 @@
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.tuner.R;
diff --git a/tuner/src/com/android/tv/tuner/setup/WelcomeFragment.java b/tuner/src/com/android/tv/tuner/setup/WelcomeFragment.java
index 2a414df..dfa994b 100644
--- a/tuner/src/com/android/tv/tuner/setup/WelcomeFragment.java
+++ b/tuner/src/com/android/tv/tuner/setup/WelcomeFragment.java
@@ -19,8 +19,8 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
+import androidx.leanback.widget.GuidanceStylist.Guidance;
+import androidx.leanback.widget.GuidedAction;
import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.tuner.R;
diff --git a/tuner/src/com/android/tv/tuner/singletons/TunerSingletons.java b/tuner/src/com/android/tv/tuner/singletons/TunerSingletons.java
index 48b17dc..dfe9005 100644
--- a/tuner/src/com/android/tv/tuner/singletons/TunerSingletons.java
+++ b/tuner/src/com/android/tv/tuner/singletons/TunerSingletons.java
@@ -18,4 +18,17 @@
import com.android.tv.common.singletons.HasTvInputId;
/** Singletons used in tuner applications */
-public interface TunerSingletons extends HasTvInputId {}
+public interface TunerSingletons extends HasTvInputId {
+
+ /*
+ * Do not add any new methods here.
+ *
+ * To move a getter to Injection.
+ * 1. Make a type injectable @Singleton.
+ * 2. Mark the getter here as deprecated.
+ * 3. Lazily inject the object in TvApplication.
+ * 4. Move easy usages of getters to injection instead.
+ * 5. Delete the method when all usages are migrated.
+ */
+
+}
diff --git a/tuner/src/com/android/tv/tuner/source/FileSourceEventDetector.java b/tuner/src/com/android/tv/tuner/source/FileSourceEventDetector.java
index 85932c8..5ee897b 100644
--- a/tuner/src/com/android/tv/tuner/source/FileSourceEventDetector.java
+++ b/tuner/src/com/android/tv/tuner/source/FileSourceEventDetector.java
@@ -24,9 +24,9 @@
import com.android.tv.tuner.data.PsipData.EitItem;
import com.android.tv.tuner.data.PsipData.SdtItem;
import com.android.tv.tuner.data.PsipData.VctItem;
+import com.android.tv.tuner.data.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.ts.EventDetector.EventListener;
import com.android.tv.tuner.ts.TsParser;
import java.util.ArrayList;
diff --git a/tuner/src/com/android/tv/tuner/source/FileTsStreamer.java b/tuner/src/com/android/tv/tuner/source/FileTsStreamer.java
index 99d37e3..15f3458 100644
--- a/tuner/src/com/android/tv/tuner/source/FileTsStreamer.java
+++ b/tuner/src/com/android/tv/tuner/source/FileTsStreamer.java
@@ -17,7 +17,9 @@
package com.android.tv.tuner.source;
import android.content.Context;
+import android.net.Uri;
import android.os.Environment;
+import android.support.annotation.Nullable;
import android.util.Log;
import android.util.SparseBooleanArray;
import com.android.tv.common.SoftPreconditions;
@@ -26,8 +28,8 @@
import com.android.tv.tuner.features.TunerFeatures;
import com.android.tv.tuner.ts.EventDetector.EventListener;
import com.android.tv.tuner.ts.TsParser;
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.upstream.DataSpec;
+import com.google.android.exoplayer2.upstream.DataSpec;
+import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -71,6 +73,7 @@
public static class FileDataSource extends TsDataSource {
private final FileTsStreamer mTsStreamer;
private final AtomicLong mLastReadPosition = new AtomicLong(0);
+ private Uri mUri;
private long mStartBufferedPosition;
private FileDataSource(FileTsStreamer tsStreamer) {
@@ -96,9 +99,10 @@
}
@Override
- public long open(DataSpec dataSpec) throws IOException {
+ public long open(DataSpec dataSpec) {
+ mUri = dataSpec.uri;
mLastReadPosition.set(0);
- return C.LENGTH_UNBOUNDED;
+ return com.google.android.exoplayer2.C.LENGTH_UNSET;
}
@Override
@@ -117,6 +121,19 @@
}
return ret;
}
+
+ // ExoPlayer V2 DataSource implementation.
+
+ @Override
+ public void addTransferListener(TransferListener transferListener) {
+ // TODO: Implement to support metrics collection.
+ }
+
+ @Nullable
+ @Override
+ public Uri getUri() {
+ return mUri;
+ }
}
/**
diff --git a/tuner/src/com/android/tv/tuner/source/TsDataSource.java b/tuner/src/com/android/tv/tuner/source/TsDataSource.java
index cf3c25d..18f4458 100644
--- a/tuner/src/com/android/tv/tuner/source/TsDataSource.java
+++ b/tuner/src/com/android/tv/tuner/source/TsDataSource.java
@@ -17,7 +17,7 @@
package com.android.tv.tuner.source;
import com.android.tv.common.compat.TvInputConstantCompat;
-import com.google.android.exoplayer.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DataSource;
/** {@link DataSource} for MPEG-TS stream, which will be used by {@link TsExtractor}. */
public abstract class TsDataSource implements DataSource {
diff --git a/tuner/src/com/android/tv/tuner/source/TsDataSourceManager.java b/tuner/src/com/android/tv/tuner/source/TsDataSourceManager.java
index 28756a9..3c00b5c 100644
--- a/tuner/src/com/android/tv/tuner/source/TsDataSourceManager.java
+++ b/tuner/src/com/android/tv/tuner/source/TsDataSourceManager.java
@@ -19,8 +19,8 @@
import android.content.Context;
import android.support.annotation.VisibleForTesting;
import com.android.tv.tuner.api.Tuner;
+import com.android.tv.tuner.data.Channel;
import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
import com.android.tv.tuner.ts.EventDetector.EventListener;
import com.google.auto.factory.AutoFactory;
import com.google.auto.factory.Provided;
diff --git a/tuner/src/com/android/tv/tuner/source/TunerTsStreamer.java b/tuner/src/com/android/tv/tuner/source/TunerTsStreamer.java
index 9e68c91..19058c8 100644
--- a/tuner/src/com/android/tv/tuner/source/TunerTsStreamer.java
+++ b/tuner/src/com/android/tv/tuner/source/TunerTsStreamer.java
@@ -17,8 +17,11 @@
package com.android.tv.tuner.source;
import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
+
import com.android.tv.common.SoftPreconditions;
import com.android.tv.tuner.api.ScanChannel;
import com.android.tv.tuner.api.Tuner;
@@ -26,8 +29,10 @@
import com.android.tv.tuner.prefs.TunerPreferences;
import com.android.tv.tuner.ts.EventDetector;
import com.android.tv.tuner.ts.EventDetector.EventListener;
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.upstream.DataSpec;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.upstream.DataSpec;
+import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -66,6 +71,7 @@
private final TunerTsStreamer mTsStreamer;
private final AtomicLong mLastReadPosition = new AtomicLong(0);
private long mStartBufferedPosition;
+ private Uri mUri;
private TunerDataSource(TunerTsStreamer tsStreamer) {
mTsStreamer = tsStreamer;
@@ -90,13 +96,16 @@
}
@Override
- public long open(DataSpec dataSpec) throws IOException {
+ public long open(DataSpec dataSpec) {
+ mUri = dataSpec.uri;
mLastReadPosition.set(0);
- return C.LENGTH_UNBOUNDED;
+ return C.LENGTH_UNSET;
}
@Override
- public void close() {}
+ public void close() {
+ mUri = null;
+ }
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
@@ -126,6 +135,18 @@
public int getSignalStrength() {
return mTsStreamer.getSignalStrength();
}
+
+ @Override
+ public void addTransferListener(TransferListener transferListener) {
+ // TODO: Implement to support metrics collection.
+ }
+
+ @Nullable
+ @Override
+ public Uri getUri() {
+ return mUri;
+ }
+
}
/**
* Creates {@link TsStreamer} for playing or recording the specified channel.
@@ -152,7 +173,8 @@
@Override
public boolean startStream(TunerChannel channel) {
if (mTunerHal.tune(
- channel.getFrequency(), channel.getModulation(), channel.getDisplayNumber(false))) {
+ channel.getDeliverySystemType().getNumber(), channel.getFrequency(),
+ channel.getModulation(), channel.getDisplayNumber(false))) {
if (channel.hasVideo()) {
mTunerHal.addPidFilter(channel.getVideoPid(), Tuner.FILTER_TYPE_VIDEO);
}
@@ -170,6 +192,7 @@
mTunerHal.addPidFilter(channel.getPcrPid(), Tuner.FILTER_TYPE_PCR);
if (mEventDetector != null) {
mEventDetector.startDetecting(
+ channel.getDeliverySystemType(),
channel.getFrequency(),
channel.getModulation(),
channel.getProgramNumber());
@@ -199,9 +222,11 @@
@Override
public boolean startStream(ScanChannel channel) {
- if (mTunerHal.tune(channel.frequency, channel.modulation, null)) {
+ if (mTunerHal.tune(channel.deliverySystemType.getNumber(), channel.frequency,
+ channel.modulation, null)) {
mEventDetector.startDetecting(
- channel.frequency, channel.modulation, EventDetector.ALL_PROGRAM_NUMBERS);
+ channel.deliverySystemType, channel.frequency, channel.modulation,
+ EventDetector.ALL_PROGRAM_NUMBERS);
synchronized (mCircularBufferMonitor) {
if (mStreaming) {
Log.w(TAG, "Streaming should be stopped before start streaming");
@@ -295,7 +320,7 @@
public void registerListener(EventListener listener) {
if (mEventDetector != null && listener != null) {
synchronized (mEventListenerActions) {
- mEventListenerActions.add(new Pair<>(listener, true));
+ mEventListenerActions.add(Pair.create(listener, true));
}
}
}
diff --git a/tuner/src/com/android/tv/tuner/ts/EventDetector.java b/tuner/src/com/android/tv/tuner/ts/EventDetector.java
index 6d1fc27..3a2d835 100644
--- a/tuner/src/com/android/tv/tuner/ts/EventDetector.java
+++ b/tuner/src/com/android/tv/tuner/ts/EventDetector.java
@@ -20,12 +20,13 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.tv.tuner.api.Tuner;
+import com.android.tv.tuner.data.Channel.DeliverySystemType;
import com.android.tv.tuner.data.PsiData;
import com.android.tv.tuner.data.PsipData;
import com.android.tv.tuner.data.PsipData.EitItem;
+import com.android.tv.tuner.data.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -51,6 +52,7 @@
private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
private final List<EventListener> mEventListeners = new ArrayList<>();
+ private DeliverySystemType mDeliverySystemType;
private int mFrequency;
private String mModulation;
private int mProgramNumber = ALL_PROGRAM_NUMBERS;
@@ -170,6 +172,7 @@
}
tunerChannel.setAudioTracks(audioTracks);
tunerChannel.setCaptionTracks(captionTracks);
+ tunerChannel.setDeliverySystemType(mDeliverySystemType);
tunerChannel.setFrequency(mFrequency);
tunerChannel.setModulation(mModulation);
mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
@@ -209,6 +212,7 @@
int channelProgramNumber = channel.getServiceId();
tunerChannel.setAudioTracks(audioTracks);
tunerChannel.setCaptionTracks(captionTracks);
+ tunerChannel.setDeliverySystemType(mDeliverySystemType);
tunerChannel.setFrequency(mFrequency);
tunerChannel.setModulation(mModulation);
mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
@@ -252,10 +256,18 @@
private void reset() {
// TODO: Use TsParser.reset()
+ int[] deliverySystemTypes = mTunerHal.getDeliverySystemTypes();
+ boolean isDvbSignal = false;
+ for (int i = 0; i < deliverySystemTypes.length; i++) {
+ if (Tuner.isDvbDeliverySystem(deliverySystemTypes[i])) {
+ isDvbSignal = true;
+ break;
+ }
+ }
mTsParser =
new TsParser(
mTsOutputListener,
- Tuner.isDvbDeliverySystem(mTunerHal.getDeliverySystemType()));
+ isDvbSignal);
mPidSet.clear();
mVctProgramNumberSet.clear();
mSdtProgramNumberSet.clear();
@@ -272,8 +284,10 @@
* @param programNumber The program number if this is for handling tune request. For scanning
* purpose, supply {@link #ALL_PROGRAM_NUMBERS}.
*/
- public void startDetecting(int frequency, String modulation, int programNumber) {
+ public void startDetecting(DeliverySystemType deliverySystemType, int frequency,
+ String modulation, int programNumber) {
reset();
+ mDeliverySystemType = deliverySystemType;
mFrequency = frequency;
mModulation = modulation;
mProgramNumber = programNumber;
diff --git a/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java b/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java
index d22b639..2053b2a 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java
@@ -21,17 +21,29 @@
import android.content.ComponentName;
import android.content.Context;
import android.media.tv.TvInputService;
+import android.net.Uri;
import android.util.Log;
+
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.tuner.source.TsDataSourceManager;
import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
+import com.android.tv.tuner.tvinput.factory.TunerRecordingSessionFactory;
import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.RemovalListener;
+
import dagger.android.AndroidInjection;
+
import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
+
import javax.inject.Inject;
/** {@link BaseTunerTvInputService} serves TV channels coming from a tuner device. */
@@ -42,10 +54,22 @@
private static final int DVR_STORAGE_CLEANUP_JOB_ID = 100;
private final Set<Session> mTunerSessions = Collections.newSetFromMap(new WeakHashMap<>());
+ private final Set<RecordingSession> mTunerRecordingSession =
+ Collections.newSetFromMap(new WeakHashMap<>());
private ChannelDataManager mChannelDataManager;
@Inject ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
@Inject TsDataSourceManager.Factory mTsDataSourceManagerFactory;
@Inject TunerSessionFactory mTunerSessionFactory;
+ @Inject TunerRecordingSessionFactory mTunerRecordingSessionFactory;
+
+ LoadingCache<String, ChannelDataManager> mChannelDataManagers;
+ RemovalListener<String, ChannelDataManager> mChannelDataManagerRemovalListener =
+ notification -> {
+ ChannelDataManager cdm = notification.getValue();
+ if (cdm != null) {
+ cdm.release();
+ }
+ };
@Override
public void onCreate() {
@@ -57,7 +81,17 @@
AndroidInjection.inject(this);
super.onCreate();
if (DEBUG) Log.d(TAG, "onCreate");
- mChannelDataManager = new ChannelDataManager(getApplicationContext());
+ mChannelDataManagers =
+ CacheBuilder.newBuilder()
+ .weakValues()
+ .removalListener(mChannelDataManagerRemovalListener)
+ .build(
+ new CacheLoader<String, ChannelDataManager>() {
+ @Override
+ public ChannelDataManager load(String inputId) {
+ return createChannelDataManager(inputId);
+ }
+ });
if (CommonFeatures.DVR.isEnabled(this)) {
JobScheduler jobScheduler =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
@@ -77,21 +111,24 @@
}
}
+ private ChannelDataManager createChannelDataManager(String inputId) {
+ return new ChannelDataManager(getApplicationContext(), inputId);
+ }
+
@Override
public void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy");
super.onDestroy();
- mChannelDataManager.release();
+ mChannelDataManagers.invalidateAll();
}
@Override
public RecordingSession onCreateRecordingSession(String inputId) {
- return new TunerRecordingSession(
- this,
- inputId,
- mChannelDataManager,
- mConcurrentDvrPlaybackFlags,
- mTsDataSourceManagerFactory);
+ RecordingSession session =
+ mTunerRecordingSessionFactory.create(
+ inputId, this::onReleased, mChannelDataManagers.getUnchecked(inputId));
+ mTunerRecordingSession.add(session);
+ return session;
}
@Override
@@ -103,8 +140,12 @@
Log.d(TAG, "abort creating an session");
return null;
}
+
final Session session =
- mTunerSessionFactory.create(this, mChannelDataManager, this::onReleased);
+ mTunerSessionFactory.create(
+ mChannelDataManagers.getUnchecked(inputId),
+ this::onReleased,
+ this::getRecordingUri);
mTunerSessions.add(session);
session.setOverlayViewEnabled(true);
return session;
@@ -115,7 +156,22 @@
}
}
+ private Uri getRecordingUri(Uri channelUri) {
+ for (RecordingSession session : mTunerRecordingSession) {
+ TunerRecordingSession tunerSession = (TunerRecordingSession) session;
+ if (tunerSession.getChannelUri().equals(channelUri)) {
+ return tunerSession.getRecordingUri();
+ }
+ }
+ return null;
+ }
+
private void onReleased(Session session) {
mTunerSessions.remove(session);
+ mChannelDataManagers.cleanUp();
+ }
+
+ private void onReleased(RecordingSession session) {
+ mTunerRecordingSession.remove(session);
}
}
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java b/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java
index 5561693..ed61f71 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java
@@ -22,33 +22,40 @@
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
+
import com.android.tv.common.compat.RecordingSessionCompat;
-import com.android.tv.tuner.source.TsDataSourceManager;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import com.android.tv.tuner.tvinput.factory.TunerRecordingSessionFactory;
+import com.android.tv.tuner.tvinput.factory.TunerRecordingSessionFactory.RecordingSessionReleasedCallback;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
/** Processes DVR recordings, and deletes the previously recorded contents. */
+@AutoFactory(
+ className = "TunerRecordingSessionFactoryImpl",
+ implementing = TunerRecordingSessionFactory.class)
public class TunerRecordingSession extends RecordingSessionCompat {
private static final String TAG = "TunerRecordingSession";
private static final boolean DEBUG = false;
private final TunerRecordingSessionWorker mSessionWorker;
+ private final RecordingSessionReleasedCallback mReleasedCallback;
+ private Uri mChannelUri;
+ private Uri mRecordingUri;
public TunerRecordingSession(
- Context context,
+ @Provided @ApplicationContext Context context,
String inputId,
+ RecordingSessionReleasedCallback releasedCallback,
ChannelDataManager channelDataManager,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
- TsDataSourceManager.Factory tsDataSourceManagerFactory) {
+ @Provided TunerRecordingSessionWorker.Factory tunerRecordingSessionWorkerFactory) {
super(context);
+ mReleasedCallback = releasedCallback;
mSessionWorker =
- new TunerRecordingSessionWorker(
- context,
- inputId,
- channelDataManager,
- this,
- concurrentDvrPlaybackFlags,
- tsDataSourceManagerFactory);
+ tunerRecordingSessionWorkerFactory.create(
+ context, inputId, channelDataManager, this);
}
// RecordingSession
@@ -69,6 +76,7 @@
Log.d(TAG, "Requesting recording session release.");
}
mSessionWorker.release();
+ mReleasedCallback.onReleased(this);
}
@MainThread
@@ -95,6 +103,7 @@
if (DEBUG) {
Log.d(TAG, "Notifying recording session tuned.");
}
+ mChannelUri = channelUri;
notifyTuned(channelUri);
}
@@ -112,6 +121,7 @@
if (DEBUG) {
Log.d(TAG, "Notifying record successfully finished.");
}
+ mRecordingUri = null;
notifyRecordingStopped(recordedProgramUri);
}
@@ -120,4 +130,19 @@
Log.w(TAG, "Notifying recording error: " + reason);
notifyError(reason);
}
+
+ public void onRecordingStatePartial(Uri recUri) {
+ if (DEBUG) {
+ Log.d(TAG, "Updating recording session state to Partial");
+ }
+ mRecordingUri = recUri;
+ }
+
+ public Uri getChannelUri() {
+ return mChannelUri;
+ }
+
+ public Uri getRecordingUri() {
+ return mRecordingUri;
+ }
}
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java b/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
index 2c0c09a..851593a 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
@@ -37,17 +37,19 @@
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
+
import androidx.tvprovider.media.tv.Program;
+
import com.android.tv.common.BaseApplication;
import com.android.tv.common.data.RecordedProgramState;
import com.android.tv.common.recording.RecordingCapability;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.util.CommonUtils;
-import com.android.tv.tuner.DvbDeviceAccessor;
import com.android.tv.tuner.data.PsipData;
import com.android.tv.tuner.data.PsipData.EitItem;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
+import com.android.tv.tuner.dvb.DvbDeviceAccessor;
import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor;
import com.android.tv.tuner.exoplayer.SampleExtractor;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
@@ -57,8 +59,11 @@
import com.android.tv.tuner.source.TsDataSourceManager;
import com.android.tv.tuner.ts.EventDetector.EventListener;
import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
+
import com.google.android.exoplayer.C;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -142,7 +147,6 @@
private static final long CHANNEL_ID_NONE = -1;
private static final int MAX_TUNING_RETRY = 6;
- private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
private final Context mContext;
private final ChannelDataManager mChannelDataManager;
@@ -169,14 +173,27 @@
private List<AtscCaptionTrack> mCaptionTracks;
private DvrStorageManager mDvrStorageManager;
+ /**
+ * Factory for {@link TunerRecordingSessionWorker}}.
+ *
+ * <p>This wrapper class keeps other classes from needing to reference the {@link AutoFactory}
+ * generated class.
+ */
+ public interface Factory {
+ TunerRecordingSessionWorker create(
+ Context context,
+ String inputId,
+ ChannelDataManager dataManager,
+ TunerRecordingSession session);
+ }
+
+ @AutoFactory(implementing = TunerRecordingSessionWorker.Factory.class)
public TunerRecordingSessionWorker(
Context context,
String inputId,
ChannelDataManager dataManager,
TunerRecordingSession session,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
- TsDataSourceManager.Factory tsDataSourceManagerFactory) {
- mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
+ @Provided TsDataSourceManager.Factory tsDataSourceManagerFactory) {
mRandom.setSeed(System.nanoTime());
mContext = context;
HandlerThread handlerThread = new HandlerThread(TAG);
@@ -217,7 +234,7 @@
if (mChannel == null || mChannel.compareTo(channel) != 0) {
return;
}
- mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget();
+ mHandler.obtainMessage(MSG_UPDATE_CC_INFO, Pair.create(channel, items)).sendToTarget();
mChannelDataManager.notifyEventDetected(channel, items);
}
@@ -362,7 +379,7 @@
}
case MSG_UPDATE_PARTIAL_STATE:
{
- updateRecordedProgram(RecordedProgramState.PARTIAL, -1, -1);
+ updateRecordedProgramStatePartial();
return true;
}
}
@@ -457,35 +474,29 @@
mDvrStorageManager = new DvrStorageManager(mStorageDir, true);
mRecorder =
new ExoPlayerSampleExtractor(
- Uri.EMPTY,
- mTunerSource,
- new BufferManager(mDvrStorageManager),
- this,
- true,
- mConcurrentDvrPlaybackFlags);
+ Uri.EMPTY, mTunerSource, new BufferManager(mDvrStorageManager), this, true);
mRecorder.setOnCompletionListener(this, mHandler);
mProgramUri = programUri;
mSessionState = STATE_RECORDING;
mRecorderRunning = true;
- if (mConcurrentDvrPlaybackFlags.enabled()) {
- mRecordedProgramUri =
- insertRecordedProgram(
- getRecordedProgram(),
- mChannel.getChannelId(),
- Uri.fromFile(mStorageDir).toString(),
- calculateRecordingSizeInBytes(),
- mRecordStartTime,
- mRecordStartTime);
- if (mRecordedProgramUri == null) {
- new DeleteRecordingTask().execute(mStorageDir);
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.e(TAG, "Inserting a recording to DB failed");
- return false;
- }
- mSession.onRecordingUri(mRecordedProgramUri.toString());
- mHandler.sendEmptyMessageDelayed(
- MSG_UPDATE_PARTIAL_STATE, MIN_PARTIAL_RECORDING_DURATION_MS);
+ mRecordedProgramUri =
+ insertRecordedProgram(
+ getRecordedProgram(),
+ mChannel.getChannelId(),
+ Uri.fromFile(mStorageDir).toString(),
+ calculateRecordingSizeInBytes(),
+ mRecordStartTime,
+ mRecordStartTime);
+ if (mRecordedProgramUri == null) {
+ new DeleteRecordingTask().execute(mStorageDir);
+ mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
+ Log.e(TAG, "Inserting a recording to DB failed");
+ return false;
}
+ mSession.onRecordingUri(mRecordedProgramUri.toString());
+ mHandler.sendEmptyMessageDelayed(
+ MSG_UPDATE_PARTIAL_STATE, MIN_PARTIAL_RECORDING_DURATION_MS);
+
mHandler.sendEmptyMessage(MSG_PREPARE_RECODER);
mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS);
@@ -592,7 +603,7 @@
if (checkRecordedProgramTable(COLUMN_SERIES_ID)) {
values.put(COLUMN_SERIES_ID, mSeriesId);
}
- if (mConcurrentDvrPlaybackFlags.enabled() && checkRecordedProgramTable(COLUMN_STATE)) {
+ if (checkRecordedProgramTable(COLUMN_STATE)) {
values.put(COLUMN_STATE, RecordedProgramState.STARTED.name());
}
if (program != null) {
@@ -602,20 +613,27 @@
.insert(TvContract.RecordedPrograms.CONTENT_URI, values);
}
- private void updateRecordedProgram(RecordedProgramState state, long endTime, long totalBytes) {
+ private void updateRecordedProgramStateFinished(long endTime, long totalBytes) {
ContentValues values = new ContentValues();
+ values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, totalBytes);
+ values.put(
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, endTime - mRecordStartTime);
+ values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
if (checkRecordedProgramTable(COLUMN_STATE)) {
- values.put(COLUMN_STATE, state.name());
- }
- if (state.equals(RecordedProgramState.FINISHED)) {
- values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, totalBytes);
- values.put(
- RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, endTime - mRecordStartTime);
- values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
+ values.put(COLUMN_STATE, RecordedProgramState.FINISHED.name());
}
mContext.getContentResolver().update(mRecordedProgramUri, values, null, null);
}
+ private void updateRecordedProgramStatePartial() {
+ mSession.onRecordingStatePartial(mRecordedProgramUri);
+ if (checkRecordedProgramTable(COLUMN_STATE)) {
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_STATE, RecordedProgramState.PARTIAL.name());
+ mContext.getContentResolver().update(mRecordedProgramUri, values, null, null);
+ }
+ }
+
private void onRecordingResult(boolean success, long lastExtractedPositionUs) {
if (mSessionState != STATE_RECORDING) {
// Error notification is not needed.
@@ -640,25 +658,7 @@
(lastExtractedPositionUs == C.UNKNOWN_TIME_US)
? System.currentTimeMillis()
: mRecordStartTime + lastExtractedPositionUs / 1000;
- if (!mConcurrentDvrPlaybackFlags.enabled()) {
- mRecordedProgramUri =
- insertRecordedProgram(
- getRecordedProgram(),
- mChannel.getChannelId(),
- Uri.fromFile(mStorageDir).toString(),
- calculateRecordingSizeInBytes(),
- mRecordStartTime,
- recordEndTime);
- if (mRecordedProgramUri == null) {
- new DeleteRecordingTask().execute(mStorageDir);
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.e(TAG, "Inserting a recording to DB failed");
- return;
- }
- } else {
- updateRecordedProgram(
- RecordedProgramState.FINISHED, recordEndTime, calculateRecordingSizeInBytes());
- }
+ updateRecordedProgramStateFinished(recordEndTime, calculateRecordingSizeInBytes());
mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks);
mSession.onRecordFinished(mRecordedProgramUri);
}
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerSession.java b/tuner/src/com/android/tv/tuner/tvinput/TunerSession.java
index fedb5f6..eb3a7d0 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerSession.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerSession.java
@@ -27,18 +27,24 @@
import android.util.Log;
import android.view.Surface;
import android.view.View;
+
import com.android.tv.common.CommonPreferences.CommonPreferencesChangedListener;
import com.android.tv.common.compat.TisSessionCompat;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.tuner.prefs.TunerPreferences;
-import com.android.tv.tuner.source.TsDataSourceManager;
import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
+import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
+import com.android.tv.tuner.tvinput.factory.TunerSessionFactory.SessionRecordingCallback;
import com.android.tv.tuner.tvinput.factory.TunerSessionFactory.SessionReleasedCallback;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
/**
* Provides a tuner TV input session. Main tuner input functions are implemented in {@link
* TunerSessionWorker}.
*/
+@AutoFactory(className = "TunerSessionV1Factory", implementing = TunerSessionFactory.class)
public class TunerSession extends TisSessionCompat implements CommonPreferencesChangedListener {
private static final String TAG = "TunerSession";
@@ -47,26 +53,26 @@
private final TunerSessionOverlay mTunerSessionOverlay;
private final TunerSessionWorker mSessionWorker;
private final SessionReleasedCallback mReleasedCallback;
+ private final SessionRecordingCallback mRecordingCallback;
private boolean mPlayPaused;
private long mTuneStartTimestamp;
public TunerSession(
- Context context,
+ @Provided @ApplicationContext Context context,
ChannelDataManager channelDataManager,
SessionReleasedCallback releasedCallback,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
- TsDataSourceManager.Factory tsDataSourceManagerFactory) {
+ SessionRecordingCallback recordingCallback,
+ @Provided TunerSessionWorker.Factory tunerSessionWorkerFactory) {
super(context);
mReleasedCallback = releasedCallback;
+ mRecordingCallback = recordingCallback;
mTunerSessionOverlay = new TunerSessionOverlay(context);
mSessionWorker =
- new TunerSessionWorker(
+ tunerSessionWorkerFactory.create(
context,
channelDataManager,
this,
- mTunerSessionOverlay,
- concurrentDvrPlaybackFlags,
- tsDataSourceManagerFactory);
+ mTunerSessionOverlay);
TunerPreferences.setCommonPreferencesChangedListener(this);
}
@@ -204,4 +210,8 @@
public void onCommonPreferencesChanged() {
mSessionWorker.sendMessage(TunerSessionWorker.MSG_TUNER_PREFERENCES_CHANGED);
}
+
+ public Uri getRecordingUri(Uri channelUri) {
+ return mRecordingCallback.getRecordingUri(channelUri);
+ }
}
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionExoV2.java b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionExoV2.java
index 4eca44d..7ebb2b2 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionExoV2.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionExoV2.java
@@ -27,15 +27,21 @@
import android.util.Log;
import android.view.Surface;
import android.view.View;
+
import com.android.tv.common.CommonPreferences.CommonPreferencesChangedListener;
import com.android.tv.common.compat.TisSessionCompat;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
import com.android.tv.tuner.prefs.TunerPreferences;
-import com.android.tv.tuner.source.TsDataSourceManager;
import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
+import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
+import com.android.tv.tuner.tvinput.factory.TunerSessionFactory.SessionRecordingCallback;
import com.android.tv.tuner.tvinput.factory.TunerSessionFactory.SessionReleasedCallback;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
/** Provides a tuner TV input session. */
+@AutoFactory(implementing = TunerSessionFactory.class)
public class TunerSessionExoV2 extends TisSessionCompat
implements CommonPreferencesChangedListener {
@@ -45,26 +51,26 @@
private final TunerSessionOverlay mTunerSessionOverlay;
private final TunerSessionWorkerExoV2 mSessionWorker;
private final SessionReleasedCallback mReleasedCallback;
+ private final SessionRecordingCallback mRecordingCallback;
private boolean mPlayPaused;
private long mTuneStartTimestamp;
public TunerSessionExoV2(
- Context context,
+ @Provided @ApplicationContext Context context,
ChannelDataManager channelDataManager,
SessionReleasedCallback releasedCallback,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
- TsDataSourceManager.Factory tsDataSourceManagerFactory) {
+ SessionRecordingCallback recordingCallback,
+ @Provided TunerSessionWorkerExoV2.Factory tunerSessionWorkerExoV2Factory) {
super(context);
mReleasedCallback = releasedCallback;
+ mRecordingCallback = recordingCallback;
mTunerSessionOverlay = new TunerSessionOverlay(context);
mSessionWorker =
- new TunerSessionWorkerExoV2(
+ tunerSessionWorkerExoV2Factory.create(
context,
channelDataManager,
this,
- mTunerSessionOverlay,
- concurrentDvrPlaybackFlags,
- tsDataSourceManagerFactory);
+ mTunerSessionOverlay);
TunerPreferences.setCommonPreferencesChangedListener(this);
}
@@ -203,4 +209,8 @@
public void onCommonPreferencesChanged() {
mSessionWorker.sendMessage(TunerSessionWorkerExoV2.MSG_TUNER_PREFERENCES_CHANGED);
}
+
+ public Uri getRecordingUri(Uri channelUri) {
+ return mRecordingCallback.getRecordingUri(channelUri);
+ }
}
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionOverlay.java b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionOverlay.java
index 9f21e16..53e0bcc 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionOverlay.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionOverlay.java
@@ -26,17 +26,18 @@
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
-import com.android.tv.common.util.SystemPropertiesProxy;
+
import com.android.tv.tuner.R;
import com.android.tv.tuner.cc.CaptionLayout;
import com.android.tv.tuner.cc.CaptionTrackRenderer;
import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.util.GlobalSettingsUtils;
import com.android.tv.tuner.util.StatusTextUtils;
/** Executes {@link Session} overlay changes on the main thread. */
/* package */ final class TunerSessionOverlay implements Handler.Callback {
+ private static final boolean DEBUG = false;
/** Displays the given {@link String} message object in the message view. */
public static final int MSG_UI_SHOW_MESSAGE = 1;
@@ -67,8 +68,6 @@
/** Displays a toast signalling that a re-scan is required. Does not expect a message object. */
public static final int MSG_UI_TOAST_RESCAN_NEEDED = 11;
- private static final String USBTUNER_SHOW_DEBUG = "persist.tv.tuner.show_debug";
-
private final Context mContext;
private final Handler mHandler;
private final View mOverlayView;
@@ -88,13 +87,12 @@
mHandler = new Handler(this);
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- boolean showDebug = SystemPropertiesProxy.getBoolean(USBTUNER_SHOW_DEBUG, false);
mOverlayView = inflater.inflate(R.layout.ut_overlay_view, null);
mMessageLayout = mOverlayView.findViewById(R.id.message_layout);
mMessageLayout.setVisibility(View.INVISIBLE);
mMessageView = mOverlayView.findViewById(R.id.message);
mStatusView = mOverlayView.findViewById(R.id.tuner_status);
- mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE);
+ mStatusView.setVisibility(DEBUG ? View.VISIBLE : View.INVISIBLE);
mAudioStatusView = mOverlayView.findViewById(R.id.audio_status);
mAudioStatusView.setVisibility(View.INVISIBLE);
CaptionLayout captionLayout = mOverlayView.findViewById(R.id.caption);
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
index d3f9409..f9bc734 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
@@ -44,22 +44,22 @@
import android.util.SparseArray;
import android.view.Surface;
import android.view.accessibility.CaptioningManager;
+
import com.android.tv.common.CommonPreferences.TrickplaySetting;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.TvContentRatingCache;
import com.android.tv.common.compat.TvInputConstantCompat;
import com.android.tv.common.customization.CustomizationManager;
import com.android.tv.common.customization.CustomizationManager.TRICKPLAY_MODE;
-import com.android.tv.common.experiments.Experiments;
+import com.android.tv.common.dev.DeveloperPreferences;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.common.util.SystemPropertiesProxy;
import com.android.tv.tuner.data.Cea708Data;
+import com.android.tv.tuner.data.Channel;
import com.android.tv.tuner.data.PsipData.EitItem;
import com.android.tv.tuner.data.PsipData.TvTracksInterface;
+import com.android.tv.tuner.data.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.exoplayer.MpegTsPlayer;
import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
@@ -74,10 +74,16 @@
import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
import com.android.tv.tuner.tvinput.debug.TunerDebug;
import com.android.tv.tuner.util.StatusTextUtils;
+
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.audio.AudioCapabilities;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
import com.google.common.collect.ImmutableList;
+
import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import com.android.tv.common.flags.LegacyFlags;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
@@ -103,8 +109,6 @@
private static final boolean DEBUG = false;
private static final boolean ENABLE_PROFILER = true;
private static final String PLAY_FROM_CHANNEL = "channel";
- private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes";
- private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB
private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB
// Public messages
@@ -189,6 +193,7 @@
private final int mMaxTrickplayBufferSizeMb;
private final File mTrickplayBufferDir;
private final @TRICKPLAY_MODE int mTrickplayModeCustomization;
+ private final LegacyFlags mLegacyFlags;
private volatile Surface mSurface;
private volatile float mVolume = 1.0f;
private volatile boolean mCaptionEnabled;
@@ -232,17 +237,36 @@
private boolean mReleaseRequested; // Guarded by mReleaseLock
private final Object mReleaseLock = new Object();
private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
+ private Uri mChannelUri;
+ private Uri mRecordingUri;
+ private boolean mOnTuneUsesRecording = false;
private int mSignalStrength;
private long mRecordedProgramStartTimeMs;
+ /**
+ * Factory for {@link TunerSessionWorker}.
+ *
+ * <p>This wrapper class keeps other classes from needing to reference the {@link AutoFactory}
+ * generated class.
+ */
+ public interface Factory {
+ public TunerSessionWorker create(
+ Context context,
+ ChannelDataManager channelDataManager,
+ TunerSession tunerSession,
+ TunerSessionOverlay tunerSessionOverlay);
+ }
+
+ @AutoFactory(implementing = TunerSessionWorker.Factory.class)
public TunerSessionWorker(
Context context,
ChannelDataManager channelDataManager,
TunerSession tunerSession,
TunerSessionOverlay tunerSessionOverlay,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
- TsDataSourceManager.Factory tsDataSourceManagerFactory) {
+ @Provided ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
+ @Provided LegacyFlags legacyFlags,
+ @Provided TsDataSourceManager.Factory tsDataSourceManagerFactory) {
this(
context,
channelDataManager,
@@ -250,6 +274,7 @@
tunerSessionOverlay,
null,
concurrentDvrPlaybackFlags,
+ legacyFlags,
tsDataSourceManagerFactory);
}
@@ -261,8 +286,10 @@
TunerSessionOverlay tunerSessionOverlay,
@Nullable Handler handler,
ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
+ LegacyFlags legacyFlags,
TsDataSourceManager.Factory tsDataSourceManagerFactory) {
this.mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
+ mLegacyFlags = legacyFlags;
if (DEBUG) Log.d(TAG, "TunerSessionWorker created");
mContext = context;
if (handler != null) {
@@ -277,6 +304,7 @@
mSession = tunerSession;
mTunerSessionOverlay = tunerSessionOverlay;
mChannelDataManager = channelDataManager;
+ mRecordingUri = null;
mChannelDataManager.setListener(this);
mChannelDataManager.checkDataVersion(mContext);
mSourceManager = tsDataSourceManagerFactory.create(false);
@@ -293,8 +321,7 @@
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
mCaptionEnabled = captioningManager.isEnabled();
mPlaybackParams.setSpeed(1.0f);
- mMaxTrickplayBufferSizeMb =
- SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF);
+ mMaxTrickplayBufferSizeMb = DeveloperPreferences.MAX_BUFFER_SIZE_MBYTES.get(context);
mTrickplayModeCustomization = CustomizationManager.getTrickplayMode(context);
if (mTrickplayModeCustomization
== CustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) {
@@ -487,6 +514,11 @@
// Final status
// notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards.
Log.i(TAG, "Player ended: end of stream");
+ if (mOnTuneUsesRecording) {
+ mRecordingUri = null;
+ mSession.notifyChannelRetuned(mChannelUri);
+ sendMessage(MSG_TUNE, mChannelUri);
+ }
if (mChannel != null) {
sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
}
@@ -515,10 +547,10 @@
@Override
public void onVideoSizeChanged(int width, int height, float pixelWidthHeight) {
if (mChannel != null && mChannel.hasVideo()) {
- updateVideoTrack(width, height);
+ updateVideoTrack(width, height, pixelWidthHeight);
}
if (mRecordingId != null) {
- updateVideoTrack(width, height);
+ updateVideoTrack(width, height, pixelWidthHeight);
}
}
@@ -532,6 +564,9 @@
} else {
mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
}
+ if (mOnTuneUsesRecording) {
+ mBufferStartTimeMs = mRecordStartTimeMs = mRecordedProgramStartTimeMs;
+ }
notifyVideoAvailable();
mReportedDrawnToSurface = true;
@@ -587,7 +622,7 @@
// ChannelDataManager.ProgramInfoListener
@Override
public void onProgramsArrived(TunerChannel channel, List<EitItem> programs) {
- sendMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(channel, programs));
+ sendMessage(MSG_SCHEDULE_OF_PROGRAMS, Pair.create(channel, programs));
}
@Override
@@ -602,7 +637,7 @@
@Override
public void onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs) {
- sendMessage(MSG_PROGRAM_DATA_RESULT, new Pair<>(channel, programs));
+ sendMessage(MSG_PROGRAM_DATA_RESULT, Pair.create(channel, programs));
}
// PlaybackBufferListener
@@ -650,7 +685,7 @@
}
private static class RecordedProgram {
- // private final long mChannelId;
+ private final long mChannelId;
private final String mDataUri;
private final long mStartTimeMillis;
@@ -662,14 +697,13 @@
public RecordedProgram(Cursor cursor) {
int index = 0;
- // mChannelId = cursor.getLong(index++);
- index++;
+ mChannelId = cursor.getLong(index++);
mDataUri = cursor.getString(index++);
mStartTimeMillis = cursor.getLong(index++);
}
public RecordedProgram(long channelId, String dataUri) {
- // mChannelId = channelId;
+ mChannelId = channelId;
mDataUri = dataUri;
mStartTimeMillis = 0;
}
@@ -689,6 +723,10 @@
public long getStartTime() {
return mStartTimeMillis;
}
+
+ public long getChannelId() {
+ return mChannelId;
+ }
}
private RecordedProgram getRecordedProgram(Uri recordedUri) {
@@ -711,9 +749,13 @@
}
}
- private String parseRecording(Uri uri) {
+ private String parseRecording(Uri uri, long channelId) {
RecordedProgram recording = getRecordedProgram(uri);
if (recording != null) {
+ if (channelId != -1 && channelId != recording.getChannelId()) {
+ // Recorded URI is of some other channel
+ return null;
+ }
mRecordedProgramStartTimeMs = recording.getStartTime();
return recording.getDataUri();
}
@@ -826,10 +868,20 @@
mIsActiveSession = true;
}
String recording = null;
+ mOnTuneUsesRecording = false;
long channelId = parseChannel(channelUri);
TunerChannel channel = (channelId == -1) ? null : mChannelDataManager.getChannel(channelId);
+ mRecordingUri = mSession.getRecordingUri(channelUri);
if (channelId == -1) {
- recording = parseRecording(channelUri);
+ recording = parseRecording(channelUri, channelId);
+
+ } else if (mRecordingUri != null && mConcurrentDvrPlaybackFlags.onTuneUsesRecording()) {
+ mChannelUri = channelUri;
+ recording = parseRecording(mRecordingUri, channelId);
+ if (recording != null) {
+ mOnTuneUsesRecording = true;
+ channel = null;
+ }
}
if (channel == null && recording == null) {
Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri);
@@ -1133,8 +1185,15 @@
if (mPlayer == null) {
return true;
}
+ long seekPosMs = timeMs;
+ if (mRecordingId != null) {
+ long systemBufferTime = System.currentTimeMillis() - SEEK_MARGIN_MS;
+ if (seekPosMs > systemBufferTime) {
+ seekPosMs = systemBufferTime;
+ }
+ }
setTrickplayEnabledIfNeeded();
- doTimeShiftSeekTo(timeMs);
+ doTimeShiftSeekTo(seekPosMs);
return true;
}
@@ -1432,8 +1491,7 @@
}
MpegTsPlayer player =
new MpegTsPlayer(
- new MpegTsRendererBuilder(
- mContext, bufferManager, this, mConcurrentDvrPlaybackFlags),
+ new MpegTsRendererBuilder(mContext, bufferManager, this),
mHandler,
mSourceManager,
capabilities,
@@ -1444,7 +1502,7 @@
player.setVideoEventListener(this);
player.setCaptionServiceNumber(
mCaptionTrack != null
- ? mCaptionTrack.serviceNumber
+ ? mCaptionTrack.getServiceNumber()
: Cea708Data.EMPTY_SERVICE_NUMBER);
return player;
}
@@ -1454,7 +1512,7 @@
mTunerSessionOverlay.sendUiMessage(
TunerSessionOverlay.MSG_UI_START_CAPTION_TRACK, mCaptionTrack);
if (mPlayer != null) {
- mPlayer.setCaptionServiceNumber(mCaptionTrack.serviceNumber);
+ mPlayer.setCaptionServiceNumber(mCaptionTrack.getServiceNumber());
}
}
}
@@ -1511,12 +1569,13 @@
}
}
- private void updateVideoTrack(int width, int height) {
+ private void updateVideoTrack(int width, int height, float pixelWidthHeight) {
removeTvTracks(TvTrackInfo.TYPE_VIDEO);
mTvTracks.add(
new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID)
.setVideoWidth(width)
.setVideoHeight(height)
+ .setVideoPixelAspectRatio(pixelWidthHeight)
.build());
mSession.notifyTracksChanged(mTvTracks);
mSession.notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID);
@@ -1530,7 +1589,7 @@
if (audioTracks != null) {
int index = 0;
for (AtscAudioTrack audioTrack : audioTracks) {
- audioTrack.index = index;
+ audioTrack = audioTrack.toBuilder().setIndex(index).build();
mAudioTrackMap.put(index, audioTrack);
++index;
}
@@ -1560,10 +1619,10 @@
String language =
!TextUtils.isEmpty(infoFromPlayer.language)
? infoFromPlayer.language
- : (infoFromEit != null && infoFromEit.language != null)
- ? infoFromEit.language
- : (infoFromVct != null && infoFromVct.language != null)
- ? infoFromVct.language
+ : (infoFromEit != null && infoFromEit.hasLanguage())
+ ? infoFromEit.getLanguage()
+ : (infoFromVct != null && infoFromVct.hasLanguage())
+ ? infoFromVct.getLanguage()
: null;
TvTrackInfo.Builder builder =
new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i);
@@ -1584,20 +1643,20 @@
mCaptionTrackMap.clear();
if (captionTracks != null) {
for (AtscCaptionTrack captionTrack : captionTracks) {
- if (mCaptionTrackMap.indexOfKey(captionTrack.serviceNumber) >= 0) {
+ if (mCaptionTrackMap.indexOfKey(captionTrack.getServiceNumber()) >= 0) {
continue;
}
- String language = captionTrack.language;
+ String language = captionTrack.getLanguage();
// The service number of the caption service is used for track id of a subtitle.
// Later, when a subtitle is chosen, track id will be passed on to TsParser.
TvTrackInfo.Builder builder =
new TvTrackInfo.Builder(
TvTrackInfo.TYPE_SUBTITLE,
- SUBTITLE_TRACK_PREFIX + captionTrack.serviceNumber);
+ SUBTITLE_TRACK_PREFIX + captionTrack.getServiceNumber());
builder.setLanguage(language);
mTvTracks.add(builder.build());
- mCaptionTrackMap.put(captionTrack.serviceNumber, captionTrack);
+ mCaptionTrackMap.put(captionTrack.getServiceNumber(), captionTrack);
}
}
mSession.notifyTracksChanged(mTvTracks);
@@ -1777,6 +1836,9 @@
} else {
mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
}
+ if (mOnTuneUsesRecording) {
+ mBufferStartTimeMs = mRecordStartTimeMs = mRecordedProgramStartTimeMs;
+ }
mLastPositionMs = 0;
mCaptionTrack = null;
mSignalStrength = TvInputConstantCompat.SIGNAL_STRENGTH_UNKNOWN;
@@ -1784,6 +1846,14 @@
mSession.notifySignalStrength(mSignalStrength);
}
mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
+ if (mOnTuneUsesRecording) {
+ mHandler.obtainMessage(
+ MSG_TIMESHIFT_SEEK_TO,
+ 1,
+ 0,
+ System.currentTimeMillis() - SEEK_MARGIN_MS)
+ .sendToTarget();
+ }
}
private void doReschedulePrograms() {
@@ -1805,7 +1875,7 @@
+ " current program: "
+ getCurrentProgram());
}
- mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(mChannel, mPrograms))
+ mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, Pair.create(mChannel, mPrograms))
.sendToTarget();
}
mHandler.removeMessages(MSG_RESCHEDULE_PROGRAMS);
@@ -1966,10 +2036,12 @@
private void doDiscoverCaptionServiceNumber(int serviceNumber) {
int index = mCaptionTrackMap.indexOfKey(serviceNumber);
if (index < 0) {
- AtscCaptionTrack captionTrack = new AtscCaptionTrack();
- captionTrack.serviceNumber = serviceNumber;
- captionTrack.wideAspectRatio = false;
- captionTrack.easyReader = false;
+ AtscCaptionTrack captionTrack =
+ AtscCaptionTrack.newBuilder()
+ .setServiceNumber(serviceNumber)
+ .setWideAspectRatio(false)
+ .setEasyReader(false)
+ .build();
mCaptionTrackMap.put(serviceNumber, captionTrack);
mTvTracks.add(
new TvTrackInfo.Builder(
@@ -1988,7 +2060,7 @@
ImmutableList<TvContentRating> ratings =
mTvContentRatingCache.getRatings(currentProgram.getContentRating());
if ((ratings == null || ratings.isEmpty())) {
- if (Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get()) {
+ if (mLegacyFlags.enableUnratedContentSettings()) {
ratings = ImmutableList.of(TvContentRating.UNRATED);
} else {
ratings = NO_CONTENT_RATINGS;
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2.java b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2.java
index 82afff1..f8a87d8 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2.java
@@ -44,22 +44,22 @@
import android.util.SparseArray;
import android.view.Surface;
import android.view.accessibility.CaptioningManager;
+
import com.android.tv.common.CommonPreferences.TrickplaySetting;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.TvContentRatingCache;
import com.android.tv.common.compat.TvInputConstantCompat;
import com.android.tv.common.customization.CustomizationManager;
import com.android.tv.common.customization.CustomizationManager.TRICKPLAY_MODE;
-import com.android.tv.common.experiments.Experiments;
+import com.android.tv.common.dev.DeveloperPreferences;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.common.util.SystemPropertiesProxy;
import com.android.tv.tuner.data.Cea708Data;
+import com.android.tv.tuner.data.Channel;
import com.android.tv.tuner.data.PsipData.EitItem;
import com.android.tv.tuner.data.PsipData.TvTracksInterface;
+import com.android.tv.tuner.data.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.Track.AtscCaptionTrack;
import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.exoplayer.MpegTsPlayer;
import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
@@ -74,10 +74,16 @@
import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
import com.android.tv.tuner.tvinput.debug.TunerDebug;
import com.android.tv.tuner.util.StatusTextUtils;
+
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.audio.AudioCapabilities;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
import com.google.common.collect.ImmutableList;
+
import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+import com.android.tv.common.flags.LegacyFlags;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
@@ -100,8 +106,6 @@
private static final boolean DEBUG = false;
private static final boolean ENABLE_PROFILER = true;
private static final String PLAY_FROM_CHANNEL = "channel";
- private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes";
- private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB
private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB
// Public messages
@@ -231,17 +235,37 @@
private boolean mReleaseRequested; // Guarded by mReleaseLock
private final Object mReleaseLock = new Object();
private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
+ private final LegacyFlags mLegacyFlags;
+ private Uri mChannelUri;
+ private Uri mRecordingUri;
+ private boolean mOnTuneUsesRecording = false;
private int mSignalStrength;
private long mRecordedProgramStartTimeMs;
+ /**
+ * Factory for {@link TunerSessionWorkerExoV2}.
+ *
+ * <p>This wrapper class keeps other classes from needing to reference the {@link AutoFactory}
+ * generated class.
+ */
+ public interface Factory {
+ public TunerSessionWorkerExoV2 create(
+ Context context,
+ ChannelDataManager channelDataManager,
+ TunerSessionExoV2 tunerSession,
+ TunerSessionOverlay tunerSessionOverlay);
+ }
+
+ @AutoFactory(implementing = TunerSessionWorkerExoV2.Factory.class)
public TunerSessionWorkerExoV2(
Context context,
ChannelDataManager channelDataManager,
TunerSessionExoV2 tunerSession,
TunerSessionOverlay tunerSessionOverlay,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
- TsDataSourceManager.Factory tsDataSourceManagerFactory) {
+ @Provided ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
+ @Provided LegacyFlags legacyFlags,
+ @Provided TsDataSourceManager.Factory tsDataSourceManagerFactory) {
this(
context,
channelDataManager,
@@ -249,6 +273,7 @@
tunerSessionOverlay,
null,
concurrentDvrPlaybackFlags,
+ legacyFlags,
tsDataSourceManagerFactory);
}
@@ -260,8 +285,10 @@
TunerSessionOverlay tunerSessionOverlay,
@Nullable Handler handler,
ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
+ LegacyFlags legacyFlags,
TsDataSourceManager.Factory tsDataSourceManagerFactory) {
mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
+ mLegacyFlags = legacyFlags;
if (DEBUG) {
Log.d(TAG, "TunerSessionWorkerExoV2 created");
}
@@ -278,6 +305,7 @@
mSession = tunerSession;
mTunerSessionOverlay = tunerSessionOverlay;
mChannelDataManager = channelDataManager;
+ mRecordingUri = null;
mChannelDataManager.setListener(this);
mChannelDataManager.checkDataVersion(mContext);
mSourceManager = tsDataSourceManagerFactory.create(false);
@@ -294,8 +322,7 @@
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
mCaptionEnabled = captioningManager.isEnabled();
mPlaybackParams.setSpeed(1.0f);
- mMaxTrickplayBufferSizeMb =
- SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF);
+ mMaxTrickplayBufferSizeMb = DeveloperPreferences.MAX_BUFFER_SIZE_MBYTES.get(context);
mTrickplayModeCustomization = CustomizationManager.getTrickplayMode(context);
if (mTrickplayModeCustomization
== CustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) {
@@ -493,6 +520,11 @@
// Final status
// notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards.
Log.i(TAG, "Player ended: end of stream");
+ if (mOnTuneUsesRecording) {
+ mRecordingUri = null;
+ mSession.notifyChannelRetuned(mChannelUri);
+ sendMessage(MSG_TUNE, mChannelUri);
+ }
if (mChannel != null) {
sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
}
@@ -521,10 +553,10 @@
@Override
public void onVideoSizeChanged(int width, int height, float pixelWidthHeight) {
if (mChannel != null && mChannel.hasVideo()) {
- updateVideoTrack(width, height);
+ updateVideoTrack(width, height, pixelWidthHeight);
}
if (mRecordingId != null) {
- updateVideoTrack(width, height);
+ updateVideoTrack(width, height, pixelWidthHeight);
}
}
@@ -540,6 +572,9 @@
} else {
mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
}
+ if (mOnTuneUsesRecording) {
+ mBufferStartTimeMs = mRecordStartTimeMs = mRecordedProgramStartTimeMs;
+ }
notifyVideoAvailable();
mReportedDrawnToSurface = true;
@@ -595,7 +630,7 @@
// ChannelDataManager.ProgramInfoListener
@Override
public void onProgramsArrived(TunerChannel channel, List<EitItem> programs) {
- sendMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(channel, programs));
+ sendMessage(MSG_SCHEDULE_OF_PROGRAMS, Pair.create(channel, programs));
}
@Override
@@ -610,7 +645,7 @@
@Override
public void onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs) {
- sendMessage(MSG_PROGRAM_DATA_RESULT, new Pair<>(channel, programs));
+ sendMessage(MSG_PROGRAM_DATA_RESULT, Pair.create(channel, programs));
}
// PlaybackBufferListener
@@ -658,7 +693,7 @@
}
private static class RecordedProgram {
- // private final long mChannelId;
+ private final long mChannelId;
private final String mDataUri;
private final long mStartTimeMillis;
@@ -670,14 +705,13 @@
public RecordedProgram(Cursor cursor) {
int index = 0;
- // mChannelId = cursor.getLong(index++);
- index++;
+ mChannelId = cursor.getLong(index++);
mDataUri = cursor.getString(index++);
mStartTimeMillis = cursor.getLong(index++);
}
public RecordedProgram(long channelId, String dataUri) {
- // mChannelId = channelId;
+ mChannelId = channelId;
mDataUri = dataUri;
mStartTimeMillis = 0;
}
@@ -697,6 +731,10 @@
public long getStartTime() {
return mStartTimeMillis;
}
+
+ public long getChannelId() {
+ return mChannelId;
+ }
}
private RecordedProgram getRecordedProgram(Uri recordedUri) {
@@ -721,9 +759,13 @@
}
}
- private String parseRecording(Uri uri) {
+ private String parseRecording(Uri uri, long channelId) {
RecordedProgram recording = getRecordedProgram(uri);
if (recording != null) {
+ if (channelId != -1 && channelId != recording.getChannelId()) {
+ // Recorded URI is of some other channel
+ return null;
+ }
mRecordedProgramStartTimeMs = recording.getStartTime();
return recording.getDataUri();
}
@@ -836,10 +878,19 @@
mIsActiveSession = true;
}
String recording = null;
+ mOnTuneUsesRecording = false;
long channelId = parseChannel(channelUri);
TunerChannel channel = (channelId == -1) ? null : mChannelDataManager.getChannel(channelId);
+ mRecordingUri = mSession.getRecordingUri(channelUri);
if (channelId == -1) {
- recording = parseRecording(channelUri);
+ recording = parseRecording(channelUri, channelId);
+ } else if (mRecordingUri != null && mConcurrentDvrPlaybackFlags.onTuneUsesRecording()) {
+ mChannelUri = channelUri;
+ recording = parseRecording(mRecordingUri, channelId);
+ if (recording != null) {
+ mOnTuneUsesRecording = true;
+ channel = null;
+ }
}
if (channel == null && recording == null) {
Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri);
@@ -1442,8 +1493,7 @@
}
MpegTsPlayer player =
new MpegTsPlayer(
- new MpegTsRendererBuilder(
- mContext, bufferManager, this, mConcurrentDvrPlaybackFlags),
+ new MpegTsRendererBuilder(mContext, bufferManager, this),
mHandler,
mSourceManager,
capabilities,
@@ -1456,7 +1506,7 @@
player.setVideoEventListener(this);
player.setCaptionServiceNumber(
mCaptionTrack != null
- ? mCaptionTrack.serviceNumber
+ ? mCaptionTrack.getServiceNumber()
: Cea708Data.EMPTY_SERVICE_NUMBER);
return player;
}
@@ -1466,7 +1516,7 @@
mTunerSessionOverlay.sendUiMessage(
TunerSessionOverlay.MSG_UI_START_CAPTION_TRACK, mCaptionTrack);
if (mPlayer != null) {
- mPlayer.setCaptionServiceNumber(mCaptionTrack.serviceNumber);
+ mPlayer.setCaptionServiceNumber(mCaptionTrack.getServiceNumber());
}
}
}
@@ -1523,12 +1573,13 @@
}
}
- private void updateVideoTrack(int width, int height) {
+ private void updateVideoTrack(int width, int height, float pixelWidthHeight) {
removeTvTracks(TvTrackInfo.TYPE_VIDEO);
mTvTracks.add(
new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID)
.setVideoWidth(width)
.setVideoHeight(height)
+ .setVideoPixelAspectRatio(pixelWidthHeight)
.build());
mSession.notifyTracksChanged(mTvTracks);
mSession.notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID);
@@ -1542,7 +1593,7 @@
if (audioTracks != null) {
int index = 0;
for (AtscAudioTrack audioTrack : audioTracks) {
- audioTrack.index = index;
+ audioTrack = audioTrack.toBuilder().setIndex(index).build();
mAudioTrackMap.put(index, audioTrack);
++index;
}
@@ -1572,10 +1623,10 @@
String language =
!TextUtils.isEmpty(infoFromPlayer.language)
? infoFromPlayer.language
- : (infoFromEit != null && infoFromEit.language != null)
- ? infoFromEit.language
- : (infoFromVct != null && infoFromVct.language != null)
- ? infoFromVct.language
+ : (infoFromEit != null && infoFromEit.hasLanguage())
+ ? infoFromEit.getLanguage()
+ : (infoFromVct != null && infoFromVct.hasLanguage())
+ ? infoFromVct.getLanguage()
: null;
TvTrackInfo.Builder builder =
new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i);
@@ -1596,20 +1647,20 @@
mCaptionTrackMap.clear();
if (captionTracks != null) {
for (AtscCaptionTrack captionTrack : captionTracks) {
- if (mCaptionTrackMap.indexOfKey(captionTrack.serviceNumber) >= 0) {
+ if (mCaptionTrackMap.indexOfKey(captionTrack.getServiceNumber()) >= 0) {
continue;
}
- String language = captionTrack.language;
+ String language = captionTrack.getLanguage();
// The service number of the caption service is used for track id of a subtitle.
// Later, when a subtitle is chosen, track id will be passed on to TsParser.
TvTrackInfo.Builder builder =
new TvTrackInfo.Builder(
TvTrackInfo.TYPE_SUBTITLE,
- SUBTITLE_TRACK_PREFIX + captionTrack.serviceNumber);
+ SUBTITLE_TRACK_PREFIX + captionTrack.getServiceNumber());
builder.setLanguage(language);
mTvTracks.add(builder.build());
- mCaptionTrackMap.put(captionTrack.serviceNumber, captionTrack);
+ mCaptionTrackMap.put(captionTrack.getServiceNumber(), captionTrack);
}
}
mSession.notifyTracksChanged(mTvTracks);
@@ -1791,6 +1842,9 @@
} else {
mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
}
+ if (mOnTuneUsesRecording) {
+ mBufferStartTimeMs = mRecordStartTimeMs = mRecordedProgramStartTimeMs;
+ }
mLastPositionMs = 0;
mCaptionTrack = null;
mSignalStrength = TvInputConstantCompat.SIGNAL_STRENGTH_UNKNOWN;
@@ -1798,6 +1852,14 @@
mSession.notifySignalStrength(mSignalStrength);
}
mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
+ if (mOnTuneUsesRecording) {
+ mHandler.obtainMessage(
+ MSG_TIMESHIFT_SEEK_TO,
+ 1,
+ 0,
+ System.currentTimeMillis() - SEEK_MARGIN_MS)
+ .sendToTarget();
+ }
}
private void doReschedulePrograms() {
@@ -1819,7 +1881,7 @@
+ " current program: "
+ getCurrentProgram());
}
- mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(mChannel, mPrograms))
+ mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, Pair.create(mChannel, mPrograms))
.sendToTarget();
}
mHandler.removeMessages(MSG_RESCHEDULE_PROGRAMS);
@@ -1980,10 +2042,13 @@
private void doDiscoverCaptionServiceNumber(int serviceNumber) {
int index = mCaptionTrackMap.indexOfKey(serviceNumber);
if (index < 0) {
- AtscCaptionTrack captionTrack = new AtscCaptionTrack();
- captionTrack.serviceNumber = serviceNumber;
- captionTrack.wideAspectRatio = false;
- captionTrack.easyReader = false;
+ AtscCaptionTrack.Builder captionTrackBuilder = AtscCaptionTrack.newBuilder();
+ AtscCaptionTrack captionTrack =
+ captionTrackBuilder
+ .setServiceNumber(serviceNumber)
+ .setWideAspectRatio(false)
+ .setEasyReader(false)
+ .build();
mCaptionTrackMap.put(serviceNumber, captionTrack);
mTvTracks.add(
new TvTrackInfo.Builder(
@@ -2002,7 +2067,7 @@
ImmutableList<TvContentRating> ratings =
mTvContentRatingCache.getRatings(currentProgram.getContentRating());
if ((ratings == null || ratings.isEmpty())) {
- if (Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get()) {
+ if (mLegacyFlags.enableUnratedContentSettings()) {
ratings = ImmutableList.of(TvContentRating.UNRATED);
} else {
ratings = NO_CONTENT_RATINGS;
diff --git a/tuner/src/com/android/tv/tuner/tvinput/datamanager/ChannelDataManager.java b/tuner/src/com/android/tv/tuner/tvinput/datamanager/ChannelDataManager.java
index 585b28b..447618a 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/datamanager/ChannelDataManager.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/datamanager/ChannelDataManager.java
@@ -29,11 +29,10 @@
import android.os.HandlerThread;
import android.os.Message;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.format.DateUtils;
import android.util.Log;
-import com.android.tv.common.singletons.HasSingletons;
-import com.android.tv.common.singletons.HasTvInputId;
import com.android.tv.common.util.PermissionUtils;
import com.android.tv.tuner.data.PsipData.EitItem;
import com.android.tv.tuner.data.TunerChannel;
@@ -51,7 +50,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-/** Manages the channel info and EPG data through {@link TvInputManager}. */
+/** Manages the channel info and EPG data for a specific inputId. */
public class ChannelDataManager implements Handler.Callback {
private static final String TAG = "ChannelDataManager";
@@ -146,9 +145,9 @@
void onChannelHandlingDone();
}
- public ChannelDataManager(Context context) {
+ public ChannelDataManager(Context context, String inputId) {
mContext = context;
- mInputId = HasSingletons.get(HasTvInputId.class, context).getEmbeddedTunerInputId();
+ mInputId = inputId;
mChannelsUri = TvContract.buildChannelsUriForInput(mInputId);
mTunerChannelMap = new ConcurrentHashMap<>();
mTunerChannelIdMap = new ConcurrentSkipListMap<>();
@@ -382,6 +381,12 @@
return false;
}
+ @NonNull
+ @Override
+ public String toString() {
+ return "ChannelDataManager[" + mInputId + "]";
+ }
+
// Private methods
private void handleEvents(TunerChannel channel, List<EitItem> items) {
long channelId = getChannelId(channel);
diff --git a/tuner/src/com/android/tv/tuner/tvinput/factory/TunerRecordingSessionFactory.java b/tuner/src/com/android/tv/tuner/tvinput/factory/TunerRecordingSessionFactory.java
new file mode 100644
index 0000000..c595075
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/tvinput/factory/TunerRecordingSessionFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.tuner.tvinput.factory;
+
+import android.media.tv.TvInputService.RecordingSession;
+
+import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
+
+/** {@link RecordingSession} factory */
+public interface TunerRecordingSessionFactory {
+
+ /** Called when a recording session is released */
+ interface RecordingSessionReleasedCallback {
+
+ /**
+ * Called when the given recording session is released.
+ *
+ * @param session The recording session that has been released.
+ */
+ void onReleased(RecordingSession session);
+ }
+
+ RecordingSession create(
+ String inputId,
+ RecordingSessionReleasedCallback releasedCallback,
+ ChannelDataManager channelDataManager);
+}
diff --git a/tuner/src/com/android/tv/tuner/tvinput/factory/TunerSessionFactory.java b/tuner/src/com/android/tv/tuner/tvinput/factory/TunerSessionFactory.java
index a27cb22..e22562a 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/factory/TunerSessionFactory.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/factory/TunerSessionFactory.java
@@ -1,7 +1,24 @@
+/*
+ * Copyright (C) 2019 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.tuner.tvinput.factory;
-import android.content.Context;
import android.media.tv.TvInputService.Session;
+import android.net.Uri;
+
import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
/** {@link android.media.tv.TvInputService.Session} factory */
@@ -18,8 +35,19 @@
void onReleased(Session session);
}
+ /** Called when recording URI is required for playback */
+ interface SessionRecordingCallback {
+
+ /**
+ * Called when recording URI is required for playback.
+ *
+ * @param channelUri for which recording URI is requested.
+ */
+ Uri getRecordingUri(Uri channelUri);
+ }
+
Session create(
- Context context,
ChannelDataManager channelDataManager,
- SessionReleasedCallback releasedCallback);
+ SessionReleasedCallback releasedCallback,
+ SessionRecordingCallback recordingCallback);
}
diff --git a/tuner/src/com/android/tv/tuner/tvinput/factory/TunerSessionFactoryImpl.java b/tuner/src/com/android/tv/tuner/tvinput/factory/TunerSessionFactoryImpl.java
deleted file mode 100644
index 54e959e..0000000
--- a/tuner/src/com/android/tv/tuner/tvinput/factory/TunerSessionFactoryImpl.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.android.tv.tuner.tvinput.factory;
-
-import android.content.Context;
-import android.media.tv.TvInputService.Session;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.tuner.tvinput.TunerSession;
-import com.android.tv.tuner.tvinput.TunerSessionExoV2;
-import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
-import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
-import com.android.tv.common.flags.TunerFlags;
-import javax.inject.Inject;
-
-/** Creates a {@link TunerSessionFactory}. */
-public class TunerSessionFactoryImpl implements TunerSessionFactory {
-
- private final TunerFlags mTunerFlags;
- private final ConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
- private final TsDataSourceManager.Factory mTsDataSourceManagerFactory;
-
- @Inject
- public TunerSessionFactoryImpl(
- TunerFlags tunerFlags,
- ConcurrentDvrPlaybackFlags concurrentDvrPlaybackFlags,
- TsDataSourceManager.Factory tsDataSourceManagerFactory) {
- mTunerFlags = tunerFlags;
- mConcurrentDvrPlaybackFlags = concurrentDvrPlaybackFlags;
- mTsDataSourceManagerFactory = tsDataSourceManagerFactory;
- }
-
- @Override
- public Session create(
- Context context,
- ChannelDataManager channelDataManager,
- SessionReleasedCallback releasedCallback) {
- return mTunerFlags.useExoplayerV2()
- ? new TunerSessionExoV2(
- context,
- channelDataManager,
- releasedCallback,
- mConcurrentDvrPlaybackFlags,
- mTsDataSourceManagerFactory)
- : new TunerSession(
- context,
- channelDataManager,
- releasedCallback,
- mConcurrentDvrPlaybackFlags,
- mTsDataSourceManagerFactory);
- }
-}
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/data/SectionParserTest.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/data/SectionParserTest.java
new file mode 100644
index 0000000..5c5e32a
--- /dev/null
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/data/SectionParserTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.tuner.data;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.tuner.data.PsipData.ContentAdvisoryDescriptor;
+import com.android.tv.tuner.data.PsipData.RatingRegion;
+import com.android.tv.tuner.data.PsipData.RegionalRating;
+import com.android.tv.tuner.data.PsipData.TsDescriptor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link com.android.tv.tuner.data.SectionParser}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class SectionParserTest {
+ private static final Map<String, String> US_RATING_MAP = new HashMap<>();
+ private static final int RATING_REGION_US = 1;
+
+ static {
+ // These mappings are from table 3 of ANSI-CEA-766-D
+ US_RATING_MAP.put("1 0 0 0 0 0 0 X", ""); // TV-None
+ US_RATING_MAP.put("0 0 0 0 0 1 0 X", "com.android.tv/US_TV/US_TV_Y");
+ US_RATING_MAP.put("0 0 0 0 0 2 0 X", "com.android.tv/US_TV/US_TV_Y7");
+ US_RATING_MAP.put("0 0 0 0 0 2 1 X", "com.android.tv/US_TV/US_TV_Y7/US_TV_FV");
+ US_RATING_MAP.put("2 0 0 0 0 0 0 X", "com.android.tv/US_TV/US_TV_G");
+ US_RATING_MAP.put("3 0 0 0 0 0 0 X", "com.android.tv/US_TV/US_TV_PG");
+ US_RATING_MAP.put("3 1 0 0 0 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_D");
+ US_RATING_MAP.put("3 0 1 0 0 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_L");
+ US_RATING_MAP.put("3 0 0 1 0 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_S");
+ US_RATING_MAP.put("3 0 0 0 1 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_V");
+ US_RATING_MAP.put("3 1 1 0 0 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_D/US_TV_L");
+ US_RATING_MAP.put("3 1 0 1 0 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_D/US_TV_S");
+ US_RATING_MAP.put("3 1 0 0 1 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_D/US_TV_V");
+ US_RATING_MAP.put("3 0 1 1 0 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_L/US_TV_S");
+ US_RATING_MAP.put("3 0 1 0 1 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_L/US_TV_V");
+ US_RATING_MAP.put("3 0 0 1 1 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_S/US_TV_V");
+ US_RATING_MAP.put(
+ "3 1 1 1 0 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_D/US_TV_L/US_TV_S");
+ US_RATING_MAP.put(
+ "3 1 1 0 1 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_D/US_TV_L/US_TV_V");
+ US_RATING_MAP.put(
+ "3 1 0 1 1 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_D/US_TV_S/US_TV_V");
+ US_RATING_MAP.put(
+ "3 0 1 1 1 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_L/US_TV_S/US_TV_V");
+ US_RATING_MAP.put(
+ "3 1 1 1 1 0 0 X", "com.android.tv/US_TV/US_TV_PG/US_TV_D/US_TV_L/US_TV_S/US_TV_V");
+ US_RATING_MAP.put("4 0 0 0 0 0 0 X", "com.android.tv/US_TV/US_TV_14");
+ US_RATING_MAP.put("4 1 0 0 0 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_D");
+ US_RATING_MAP.put("4 0 1 0 0 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_L");
+ US_RATING_MAP.put("4 0 0 1 0 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_S");
+ US_RATING_MAP.put("4 0 0 0 1 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_V");
+ US_RATING_MAP.put("4 1 1 0 0 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_L");
+ US_RATING_MAP.put("4 1 0 1 0 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_S");
+ US_RATING_MAP.put("4 1 0 0 1 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_V");
+ US_RATING_MAP.put("4 0 1 1 0 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_L/US_TV_S");
+ US_RATING_MAP.put("4 0 1 0 1 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_L/US_TV_V");
+ US_RATING_MAP.put("4 0 0 1 1 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_S/US_TV_V");
+ US_RATING_MAP.put(
+ "4 1 1 1 0 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_L/US_TV_S");
+ US_RATING_MAP.put(
+ "4 1 1 0 1 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_L/US_TV_V");
+ US_RATING_MAP.put(
+ "4 1 0 1 1 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_S/US_TV_V");
+ US_RATING_MAP.put(
+ "4 0 1 1 1 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_L/US_TV_S/US_TV_V");
+ US_RATING_MAP.put(
+ "4 1 1 1 1 0 0 X", "com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_L/US_TV_S/US_TV_V");
+ US_RATING_MAP.put("5 0 0 0 0 0 0 X", "com.android.tv/US_TV/US_TV_MA");
+ US_RATING_MAP.put("5 0 1 0 0 0 0 X", "com.android.tv/US_TV/US_TV_MA/US_TV_L");
+ US_RATING_MAP.put("5 0 0 1 0 0 0 X", "com.android.tv/US_TV/US_TV_MA/US_TV_S");
+ US_RATING_MAP.put("5 0 0 0 1 0 0 X", "com.android.tv/US_TV/US_TV_MA/US_TV_V");
+ US_RATING_MAP.put("5 0 1 1 0 0 0 X", "com.android.tv/US_TV/US_TV_MA/US_TV_L/US_TV_S");
+ US_RATING_MAP.put("5 0 1 0 1 0 0 X", "com.android.tv/US_TV/US_TV_MA/US_TV_L/US_TV_V");
+ US_RATING_MAP.put("5 0 0 1 1 0 0 X", "com.android.tv/US_TV/US_TV_MA/US_TV_S/US_TV_V");
+ US_RATING_MAP.put(
+ "5 0 1 1 1 0 0 X", "com.android.tv/US_TV/US_TV_MA/US_TV_L/US_TV_S/US_TV_V");
+ US_RATING_MAP.put("X X X X X X X 1", ""); // MPAA-N/A
+ US_RATING_MAP.put("X X X X X X X 2", "com.android.tv/US_MV/US_MV_G");
+ US_RATING_MAP.put("X X X X X X X 3", "com.android.tv/US_MV/US_MV_PG");
+ US_RATING_MAP.put("X X X X X X X 4", "com.android.tv/US_MV/US_MV_PG13");
+ US_RATING_MAP.put("X X X X X X X 5", "com.android.tv/US_MV/US_MV_R");
+ US_RATING_MAP.put("X X X X X X X 6", "com.android.tv/US_MV/US_MV_NC17");
+ // MPAA-X was replaced by NC17
+ US_RATING_MAP.put("X X X X X X X 7", "com.android.tv/US_MV/US_MV_NC17");
+ US_RATING_MAP.put("X X X X X X X 8", ""); // MPAA - Not Rated
+ }
+
+ @Test
+ public void testGenerateContentRating_emptyInput() {
+ assertThat(SectionParser.generateContentRating(new ArrayList<TsDescriptor>())).isEmpty();
+ }
+
+ @Test
+ public void testGenerateContentRating_validInputs() {
+ for (Map.Entry<String, String> entry : US_RATING_MAP.entrySet()) {
+ RatingRegion ratingRegion = createRatingRegionForTest(entry.getKey(), RATING_REGION_US);
+ ContentAdvisoryDescriptor descriptor = createDescriptorForTest(ratingRegion);
+ assertWithMessage("key = " + entry.getKey())
+ .that(
+ SectionParser.generateContentRating(
+ Collections.singletonList((TsDescriptor) descriptor)))
+ .isEqualTo(entry.getValue());
+ }
+ }
+
+ @Test
+ public void testGenerateContentRating_invalidInput() {
+ // Invalid because the value of the first dimension is lost.
+ RatingRegion ratingRegion = createRatingRegionForTest("X 1 0 0 0 0 0 X", RATING_REGION_US);
+ ContentAdvisoryDescriptor descriptor = createDescriptorForTest(ratingRegion);
+ assertThat(
+ SectionParser.generateContentRating(
+ Collections.singletonList((TsDescriptor) descriptor)))
+ .isEmpty();
+ }
+
+ @Test
+ public void testGenerateContentRating_multipleRatings() {
+ // TV-MA
+ RatingRegion ratingRegionTv =
+ createRatingRegionForTest("5 0 0 0 0 0 0 X", RATING_REGION_US);
+ // MPAA-R
+ RatingRegion ratingRegionMv =
+ createRatingRegionForTest("X X X X X X X 5", RATING_REGION_US);
+ ContentAdvisoryDescriptor descriptorTv = createDescriptorForTest(ratingRegionTv);
+ ContentAdvisoryDescriptor descriptorMv = createDescriptorForTest(ratingRegionMv);
+ assertThat(
+ SectionParser.generateContentRating(
+ Arrays.<TsDescriptor>asList(descriptorTv, descriptorMv)))
+ .isEqualTo("com.android.tv/US_MV/US_MV_R,com.android.tv/US_TV/US_TV_MA");
+ }
+
+ private static RatingRegion createRatingRegionForTest(String values, int region) {
+ String[] valueArray = values.split(" ");
+ List<RegionalRating> regionalRatings = new ArrayList<>();
+ for (int i = 0; i < valueArray.length; i++) {
+ try {
+ int value = Integer.valueOf(valueArray[i]);
+ if (value != 0) {
+ // value 0 means the dimension should be omitted from the descriptor
+ regionalRatings.add(new RegionalRating(i, value));
+ }
+ } catch (NumberFormatException e) {
+ // do nothing
+ }
+ }
+ return new RatingRegion(region, "", regionalRatings);
+ }
+
+ private static ContentAdvisoryDescriptor createDescriptorForTest(RatingRegion... regions) {
+ return new ContentAdvisoryDescriptor(Arrays.asList(regions));
+ }
+}
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/dvb/DvbTunerHalTest.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/dvb/DvbTunerHalTest.java
new file mode 100644
index 0000000..f26a4f1
--- /dev/null
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/dvb/DvbTunerHalTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 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.tuner.dvb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.common.compat.TvInputConstantCompat;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.tuner.tvinput.TunerSessionWorker;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link TunerSessionWorker}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
+public class DvbTunerHalTest {
+ private int mSignal = 0;
+
+ DvbTunerHal mDvbTunerHal =
+ new DvbTunerHal(RuntimeEnvironment.application) {
+ @Override
+ protected int nativeGetSignalStrength(long deviceId) {
+ return mSignal;
+ }
+ };
+
+ @Test
+ public void getSignalStrength_notUsed() {
+ mSignal = -3;
+ int signal = mDvbTunerHal.getSignalStrength();
+ assertThat(signal).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_NOT_USED);
+ }
+
+ @Test
+ public void getSignalStrength_errorMax() {
+ mSignal = Integer.MAX_VALUE;
+ int signal = mDvbTunerHal.getSignalStrength();
+ assertThat(signal).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_ERROR);
+ }
+
+ @Test
+ public void getSignalStrength_errorMin() {
+ mSignal = Integer.MIN_VALUE;
+ int signal = mDvbTunerHal.getSignalStrength();
+ assertThat(signal).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_ERROR);
+ }
+
+ @Test
+ public void getSignalStrength_error() {
+ mSignal = -1;
+ int signal = mDvbTunerHal.getSignalStrength();
+ assertThat(signal).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_ERROR);
+ }
+
+ @Test
+ public void getSignalStrength_curvedMax() {
+ mSignal = 65535;
+ int signal = mDvbTunerHal.getSignalStrength();
+ assertThat(signal).isEqualTo(100);
+ }
+
+ @Test
+ public void getSignalStrength_curvedHalf() {
+ mSignal = 58982;
+ int signal = mDvbTunerHal.getSignalStrength();
+ assertThat(signal).isEqualTo(50);
+ }
+
+ @Test
+ public void getSignalStrength_curvedMin() {
+ mSignal = 0;
+ int signal = mDvbTunerHal.getSignalStrength();
+ assertThat(signal).isEqualTo(0);
+ }
+}
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/exoplayer/tests/AssetDataSource.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/exoplayer/tests/AssetDataSource.java
new file mode 100644
index 0000000..52faa1d
--- /dev/null
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/exoplayer/tests/AssetDataSource.java
@@ -0,0 +1,134 @@
+/*
+ * 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.tuner.exoplayer.tests;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.net.Uri;
+import com.google.android.exoplayer.C;
+import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DataSpec;
+import com.google.android.exoplayer2.upstream.TransferListener;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** A local asset. */
+// Copied from com.google.android.exoplayer.upstream.AssetDataSource for test.
+final class AssetDataSource implements DataSource {
+ /** Thrown when an {@link IOException} is encountered reading a local asset. */
+ private static final class AssetDataSourceException extends IOException {
+ private AssetDataSourceException(IOException cause) {
+ super(cause);
+ }
+ }
+
+ private final AssetManager mAssetManager;
+
+ private InputStream mInputStream;
+ private long mBytesRemaining;
+ private Uri mUri;
+
+ /** Constructs a new {@link DataSource} that retrieves data from a local asset. */
+ AssetDataSource(Context context) {
+ mAssetManager = context.getAssets();
+ }
+
+ @Override
+ public long open(DataSpec dataSpec) throws AssetDataSourceException {
+ try {
+ String path = dataSpec.uri.getPath();
+ if (path.startsWith("/android_asset/")) {
+ path = path.substring(15);
+ } else if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ mInputStream = mAssetManager.open(path, AssetManager.ACCESS_RANDOM);
+ long skipped = mInputStream.skip(dataSpec.position);
+ if (skipped < dataSpec.position) {
+ // mAssetManager.open() returns an AssetInputStream, whose skip() implementation
+ // only skips fewer bytes than requested if the skip is beyond the end of the
+ // asset's data.
+ throw new EOFException();
+ }
+ if (dataSpec.length != C.LENGTH_UNBOUNDED) {
+ mBytesRemaining = dataSpec.length;
+ } else {
+ mBytesRemaining = mInputStream.available();
+ if (mBytesRemaining == Integer.MAX_VALUE) {
+ // mAssetManager.open() returns an AssetInputStream, whose available()
+ // implementation returns Integer.MAX_VALUE if the remaining length is greater
+ // than (or equal to) Integer.MAX_VALUE. We don't know the true length in this
+ // case, so treat as unbounded.
+ mBytesRemaining = C.LENGTH_UNBOUNDED;
+ }
+ }
+ } catch (IOException e) {
+ throw new AssetDataSourceException(e);
+ }
+
+ mUri = dataSpec.uri;
+ return mBytesRemaining;
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int readLength) throws AssetDataSourceException {
+ if (mBytesRemaining == 0) {
+ return -1;
+ } else {
+ int bytesRead = 0;
+ try {
+ int bytesToRead =
+ mBytesRemaining == C.LENGTH_UNBOUNDED
+ ? readLength
+ : (int) Math.min(mBytesRemaining, readLength);
+ bytesRead = mInputStream.read(buffer, offset, bytesToRead);
+ } catch (IOException e) {
+ throw new AssetDataSourceException(e);
+ }
+
+ if (bytesRead > 0 && mBytesRemaining != C.LENGTH_UNBOUNDED) {
+ mBytesRemaining -= bytesRead;
+ }
+
+ return bytesRead;
+ }
+ }
+
+ @Override
+ public void close() throws AssetDataSourceException {
+ mUri = null;
+ if (mInputStream != null) {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ throw new AssetDataSourceException(e);
+ } finally {
+ mInputStream = null;
+ }
+ }
+ }
+
+ @Override
+ public void addTransferListener(TransferListener transferListener) {
+ // TODO: Implement to support metrics collection.
+ }
+
+ @Override
+ public Uri getUri() {
+ return mUri;
+ }
+}
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/exoplayer/tests/SampleSourceExtractorTest.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/exoplayer/tests/SampleSourceExtractorTest.java
new file mode 100644
index 0000000..efafc9f
--- /dev/null
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/exoplayer/tests/SampleSourceExtractorTest.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2015 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.tuner.exoplayer.tests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static junit.framework.Assert.fail;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Pair;
+
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor;
+import com.android.tv.tuner.exoplayer.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager;
+import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
+import com.android.tv.tuner.exoplayer.buffer.SampleChunk;
+import com.android.tv.tuner.testing.buffer.VerySlowSampleChunk;
+
+import com.google.android.exoplayer.MediaFormat;
+import com.google.android.exoplayer.SampleHolder;
+import com.google.android.exoplayer.SampleSource;
+import com.google.android.exoplayer2.upstream.DataSource;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+
+/** Tests for {@link ExoPlayerSampleExtractor} */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class SampleSourceExtractorTest {
+ // Maximum bandwidth of 1080p channel is about 2.2MB/s. 2MB for a sample will suffice.
+ private static final int SAMPLE_BUFFER_SIZE = 1024 * 1024 * 2;
+ private static final int CONSUMING_SAMPLES_PERIOD = 100;
+ private Uri testStreamUri;
+ private HandlerThread handlerThread;
+ private DataSource dataSource;
+
+ @Before
+ public void setUp() {
+ testStreamUri = Uri.parse("asset:///capture_stream.ts");
+ handlerThread = new HandlerThread("test");
+ dataSource = new AssetDataSource(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void testTrickplayDisabled() throws Throwable {
+ DataSource source = new AssetDataSource(RuntimeEnvironment.application);
+ MockPlaybackBufferListener listener = new MockPlaybackBufferListener();
+ ExoPlayerSampleExtractor extractor =
+ new ExoPlayerSampleExtractor(
+ testStreamUri,
+ source,
+ null,
+ listener,
+ false,
+ Looper.getMainLooper(),
+ handlerThread);
+ assertWithMessage("Trickplay should be disabled").that(listener.getLastState()).isFalse();
+ // Prepares the extractor.
+ extractor.prepare();
+ // Looper is nat available until prepare is called at least once
+ Looper handlerLooper = handlerThread.getLooper();
+ try {
+ while (!extractor.prepare()) {
+
+ ShadowLooper.getShadowMainLooper().runOneTask();
+ Shadows.shadowOf(handlerLooper).runOneTask();
+ }
+ } catch (IOException e) {
+ fail("Exception occurred while preparing: " + e.getMessage());
+ }
+ // Selects all tracks.
+ List<MediaFormat> trackFormats = extractor.getTrackFormats();
+ for (int i = 0; i < trackFormats.size(); ++i) {
+ extractor.selectTrack(i);
+ }
+ // Consumes over some period.
+ SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
+ sampleHolder.ensureSpaceForWrite(SAMPLE_BUFFER_SIZE);
+
+ Shadows.shadowOf(handlerLooper).idle();
+ for (int i = 0; i < CONSUMING_SAMPLES_PERIOD; ++i) {
+ boolean found = false;
+ while (!found) {
+ for (int j = 0; j < trackFormats.size(); ++j) {
+ int result = extractor.readSample(j, sampleHolder);
+ switch (result) {
+ case SampleSource.SAMPLE_READ:
+ found = true;
+ break;
+ case SampleSource.END_OF_STREAM:
+ fail("Failed to read samples");
+ break;
+ default:
+ }
+ if (found) {
+ break;
+ }
+ }
+ Shadows.shadowOf(handlerLooper).runOneTask();
+ ShadowLooper.getShadowMainLooper().runOneTask();
+ }
+ }
+ extractor.release();
+ }
+
+ @Ignore("b/70338667")
+ @Test
+ public void testDiskTooSlowTrickplayDisabled() throws Throwable {
+ StorageManager storageManager = new StubStorageManager(RuntimeEnvironment.application);
+ BufferManager bufferManager =
+ new BufferManager(
+ storageManager, new VerySlowSampleChunk.VerySlowSampleChunkCreator());
+ bufferManager.setMinimumSampleSizeForSpeedCheck(0);
+ MockPlaybackBufferListener listener = new MockPlaybackBufferListener();
+ ExoPlayerSampleExtractor extractor =
+ new ExoPlayerSampleExtractor(
+ testStreamUri,
+ dataSource,
+ bufferManager,
+ listener,
+ false,
+ Looper.getMainLooper(),
+ handlerThread);
+
+ assertWithMessage("Trickplay should be enabled at the first")
+ .that(Boolean.TRUE)
+ .isEqualTo(listener.getLastState());
+ // Prepares the extractor.
+ extractor.prepare();
+ // Looper is nat available until prepare is called at least once
+ Looper handlerLooper = handlerThread.getLooper();
+ try {
+ while (!extractor.prepare()) {
+
+ ShadowLooper.getShadowMainLooper().runOneTask();
+ Shadows.shadowOf(handlerLooper).runOneTask();
+ }
+ } catch (IOException e) {
+ fail("Exception occurred while preparing: " + e.getMessage());
+ }
+ // Selects all tracks.
+ List<MediaFormat> trackFormats = extractor.getTrackFormats();
+ for (int i = 0; i < trackFormats.size(); ++i) {
+ extractor.selectTrack(i);
+ }
+ // Consumes until once speed check is done.
+ SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
+ sampleHolder.ensureSpaceForWrite(SAMPLE_BUFFER_SIZE);
+ while (!bufferManager.hasSpeedCheckDone()) {
+ boolean found = false;
+ while (!found) {
+ for (int j = 0; j < trackFormats.size(); ++j) {
+ int result = extractor.readSample(j, sampleHolder);
+ switch (result) {
+ case SampleSource.SAMPLE_READ:
+ found = true;
+ break;
+ case SampleSource.END_OF_STREAM:
+ fail("Failed to read samples");
+ break;
+ default:
+ }
+ if (found) {
+ break;
+ }
+ }
+ ShadowLooper.getShadowMainLooper().runOneTask();
+ Shadows.shadowOf(handlerLooper).runOneTask();
+ }
+ }
+ extractor.release();
+ ShadowLooper.getShadowMainLooper().idle();
+ Shadows.shadowOf(handlerLooper).idle();
+ assertWithMessage("Disk too slow event should be reported")
+ .that(listener.isReportedDiskTooSlow())
+ .isTrue();
+ }
+
+ private static class StubStorageManager implements StorageManager {
+ private final Context mContext;
+
+ StubStorageManager(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public File getBufferDir() {
+ return mContext.getCacheDir();
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return false;
+ }
+
+ @Override
+ public boolean reachedStorageMax(long bufferSize, long pendingDelete) {
+ return false;
+ }
+
+ @Override
+ public boolean hasEnoughBuffer(long pendingDelete) {
+ return true;
+ }
+
+ @Override
+ public List<BufferManager.TrackFormat> readTrackInfoFiles(boolean isAudio) {
+ return null;
+ }
+
+ @Override
+ public ArrayList<BufferManager.PositionHolder> readIndexFile(String trackId)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio)
+ throws IOException {
+ // No-op.
+ }
+
+ @Override
+ public void writeIndexFile(
+ String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
+ throws IOException {
+ // No-op.
+ }
+
+ @Override
+ public void updateIndexFile(
+ String trackName, int size, long position, SampleChunk sampleChunk, int offset)
+ throws IOException {
+ // No-op
+ }
+ }
+
+ public static class MockPlaybackBufferListener implements PlaybackBufferListener {
+ private Boolean mLastState;
+ private boolean mIsReportedDiskTooSlow;
+
+ public Boolean getLastState() {
+ return mLastState;
+ }
+
+ public boolean isReportedDiskTooSlow() {
+ return mIsReportedDiskTooSlow;
+ }
+ // PlaybackBufferListener
+ @Override
+ public void onBufferStartTimeChanged(long startTimeMs) {
+ // No-op.
+ }
+
+ @Override
+ public void onBufferStateChanged(boolean available) {
+ mLastState = available;
+ }
+
+ @Override
+ public void onDiskTooSlow() {
+ mIsReportedDiskTooSlow = true;
+ }
+ }
+}
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2Test.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2Test.java
new file mode 100644
index 0000000..ad67bb0
--- /dev/null
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2Test.java
@@ -0,0 +1,203 @@
+/*
+ * 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.tuner.tvinput;
+
+import static com.android.tv.common.customization.CustomizationManager.TRICKPLAY_MODE_ENABLED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.accessibility.CaptioningManager;
+
+import com.android.tv.common.CommonConstants;
+import com.android.tv.common.CommonPreferences;
+import com.android.tv.common.compat.TvInputConstantCompat;
+import com.android.tv.common.customization.CustomizationManager;
+import com.android.tv.common.flags.impl.DefaultConcurrentDvrPlaybackFlags;
+import com.android.tv.common.flags.impl.DefaultLegacyFlags;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.tuner.exoplayer.MpegTsPlayer;
+import com.android.tv.tuner.source.TsDataSourceManager;
+import com.android.tv.tuner.source.TunerTsStreamerManager;
+import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
+
+import com.google.android.exoplayer.audio.AudioCapabilities;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowContextImpl;
+
+import java.lang.reflect.Field;
+
+import javax.inject.Provider;
+
+/** Tests for {@link TunerSessionWorker}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
+public class TunerSessionWorkerExoV2Test {
+
+ private TunerSessionWorkerExoV2 tunerSessionWorker;
+ private int mSignalStrength = TvInputConstantCompat.SIGNAL_STRENGTH_UNKNOWN;
+ private MpegTsPlayer mPlayer = Mockito.mock(MpegTsPlayer.class);
+ private Handler mHandler;
+ private DefaultConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
+ private DefaultLegacyFlags mLegacyFlags;
+
+ @Before
+ public void setUp() throws NoSuchFieldException, IllegalAccessException {
+ Application context = RuntimeEnvironment.application;
+ mConcurrentDvrPlaybackFlags = new DefaultConcurrentDvrPlaybackFlags();
+ mLegacyFlags = DefaultLegacyFlags.DEFAULT;
+ CaptioningManager captioningManager = Mockito.mock(CaptioningManager.class);
+
+ // TODO (b/65160115)
+ Field field = CustomizationManager.class.getDeclaredField("sCustomizationPackage");
+ field.setAccessible(true);
+ field.set(null, CommonConstants.BASE_PACKAGE + ".tuner");
+ field = CustomizationManager.class.getDeclaredField("sTrickplayMode");
+ field.setAccessible(true);
+ field.set(null, TRICKPLAY_MODE_ENABLED);
+
+ ShadowContextImpl shadowContext = Shadow.extract(context.getBaseContext());
+ shadowContext.setSystemService(Context.CAPTIONING_SERVICE, captioningManager);
+
+ CommonPreferences.initialize(context);
+ ChannelDataManager channelDataManager = new ChannelDataManager(context, "testInput");
+
+ mHandler = new Handler(Looper.getMainLooper(), null);
+
+ Provider<TunerTsStreamerManager> tsStreamerManagerProvider =
+ () -> new TunerTsStreamerManager(null);
+ TsDataSourceManager.Factory tsDataSourceManagerFactory =
+ new TsDataSourceManager.Factory(tsStreamerManagerProvider);
+ new TunerSessionExoV2(
+ context,
+ channelDataManager,
+ session -> {},
+ recordingSession -> Uri.parse("recordingUri"),
+ (context1, channelDataManager1, tunerSession1, tunerSessionOverlay) -> {
+ tunerSessionWorker =
+ new TunerSessionWorkerExoV2(
+ context1,
+ channelDataManager1,
+ tunerSession1,
+ tunerSessionOverlay,
+ mHandler,
+ mConcurrentDvrPlaybackFlags,
+ mLegacyFlags,
+ tsDataSourceManagerFactory) {
+ @Override
+ protected void notifySignal(int signal) {
+ mSignalStrength = signal;
+ }
+
+ @Override
+ protected MpegTsPlayer createPlayer(
+ AudioCapabilities capabilities) {
+ return mPlayer;
+ }
+ };
+ return tunerSessionWorker;
+ });
+ }
+
+ @Test
+ public void doSelectTrack_mPlayerIsNull() {
+ Message msg = new Message();
+ msg.what = TunerSessionWorker.MSG_SELECT_TRACK;
+ assertThat(tunerSessionWorker.handleMessage(msg)).isFalse();
+ }
+
+ @Test
+ public void doCheckSignalStrength_mPlayerIsNull() {
+ Message msg = new Message();
+ msg.what = TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH;
+ assertThat(tunerSessionWorker.handleMessage(msg)).isFalse();
+ }
+
+ @Test
+ public void handleSignal_isNotUsed() {
+ assertThat(tunerSessionWorker.handleSignal(TvInputConstantCompat.SIGNAL_STRENGTH_NOT_USED))
+ .isTrue();
+ assertThat(mSignalStrength).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_NOT_USED);
+ }
+
+ @Test
+ public void handleSignal_isError() {
+ assertThat(tunerSessionWorker.handleSignal(TvInputConstantCompat.SIGNAL_STRENGTH_ERROR))
+ .isTrue();
+ assertThat(mSignalStrength).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_ERROR);
+ }
+
+ @Test
+ public void handleSignal_isUnknown() {
+ assertThat(tunerSessionWorker.handleSignal(TvInputConstantCompat.SIGNAL_STRENGTH_UNKNOWN))
+ .isTrue();
+ assertThat(mSignalStrength).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_UNKNOWN);
+ }
+
+ @Test
+ public void handleSignal_isNotifySignal() {
+ assertThat(tunerSessionWorker.handleSignal(100)).isTrue();
+ assertThat(mSignalStrength).isEqualTo(100);
+ }
+
+ @Test
+ public void preparePlayback_playerIsNotReady() {
+ Mockito.when(
+ mPlayer.prepare(
+ Mockito.eq(RuntimeEnvironment.application),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any()))
+ .thenReturn(false);
+ tunerSessionWorker.preparePlayback();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_TUNE)).isFalse();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_RETRY_PLAYBACK)).isTrue();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH)).isFalse();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL)).isFalse();
+ }
+
+ @Test
+ @Ignore
+ public void preparePlayback_playerIsReady() {
+ Mockito.when(
+ mPlayer.prepare(
+ RuntimeEnvironment.application,
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any()))
+ .thenReturn(true);
+ tunerSessionWorker.preparePlayback();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_RETRY_PLAYBACK)).isFalse();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH)).isTrue();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL)).isTrue();
+ }
+}
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerTest.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerTest.java
new file mode 100644
index 0000000..e3fc129
--- /dev/null
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.tuner.tvinput;
+
+import static com.android.tv.common.customization.CustomizationManager.TRICKPLAY_MODE_ENABLED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.accessibility.CaptioningManager;
+
+import com.android.tv.common.CommonConstants;
+import com.android.tv.common.CommonPreferences;
+import com.android.tv.common.compat.TvInputConstantCompat;
+import com.android.tv.common.customization.CustomizationManager;
+import com.android.tv.common.flags.impl.DefaultConcurrentDvrPlaybackFlags;
+import com.android.tv.common.flags.impl.DefaultLegacyFlags;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.tuner.exoplayer.MpegTsPlayer;
+import com.android.tv.tuner.source.TsDataSourceManager;
+import com.android.tv.tuner.source.TunerTsStreamerManager;
+import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
+
+import com.google.android.exoplayer.audio.AudioCapabilities;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowContextImpl;
+
+import java.lang.reflect.Field;
+
+import javax.inject.Provider;
+
+/** Tests for {@link TunerSessionWorker}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
+public class TunerSessionWorkerTest {
+
+ private TunerSessionWorker tunerSessionWorker;
+ private int mSignalStrength = TvInputConstantCompat.SIGNAL_STRENGTH_UNKNOWN;
+ private MpegTsPlayer mPlayer = Mockito.mock(MpegTsPlayer.class);
+ private Handler mHandler;
+ private DefaultConcurrentDvrPlaybackFlags mConcurrentDvrPlaybackFlags;
+ private DefaultLegacyFlags mLegacyFlags;
+
+ @Before
+ public void setUp() throws NoSuchFieldException, IllegalAccessException {
+ Application context = RuntimeEnvironment.application;
+ CaptioningManager captioningManager = Mockito.mock(CaptioningManager.class);
+ mConcurrentDvrPlaybackFlags = new DefaultConcurrentDvrPlaybackFlags();
+ mLegacyFlags = DefaultLegacyFlags.DEFAULT;
+
+ // TODO (b/65160115)
+ Field field = CustomizationManager.class.getDeclaredField("sCustomizationPackage");
+ field.setAccessible(true);
+ field.set(null, CommonConstants.BASE_PACKAGE + ".tuner");
+ field = CustomizationManager.class.getDeclaredField("sTrickplayMode");
+ field.setAccessible(true);
+ field.set(null, TRICKPLAY_MODE_ENABLED);
+
+ ShadowContextImpl shadowContext = Shadow.extract(context.getBaseContext());
+ shadowContext.setSystemService(Context.CAPTIONING_SERVICE, captioningManager);
+
+ CommonPreferences.initialize(context);
+ ChannelDataManager channelDataManager = new ChannelDataManager(context, "testInput");
+
+ mHandler = new Handler(Looper.getMainLooper(), null);
+ Provider<TunerTsStreamerManager> tsStreamerManagerProvider =
+ () -> new TunerTsStreamerManager(null);
+ TsDataSourceManager.Factory tsdm =
+ new TsDataSourceManager.Factory(tsStreamerManagerProvider);
+
+ new TunerSession(
+ context,
+ channelDataManager,
+ session -> {},
+ recordingSession -> Uri.parse("recordingUri"),
+ (context1, channelDataManager1, tunerSession1, tunerSessionOverlay) -> {
+ tunerSessionWorker =
+ new TunerSessionWorker(
+ context1,
+ channelDataManager1,
+ tunerSession1,
+ new TunerSessionOverlay(context1),
+ mHandler,
+ mConcurrentDvrPlaybackFlags,
+ mLegacyFlags,
+ tsdm) {
+ @Override
+ protected void notifySignal(int signal) {
+ mSignalStrength = signal;
+ }
+
+ @Override
+ protected MpegTsPlayer createPlayer(
+ AudioCapabilities capabilities) {
+ return mPlayer;
+ }
+ };
+ return tunerSessionWorker;
+ });
+ }
+
+ @Test
+ public void doSelectTrack_mPlayerIsNull() {
+ Message msg = new Message();
+ msg.what = TunerSessionWorker.MSG_SELECT_TRACK;
+ assertThat(tunerSessionWorker.handleMessage(msg)).isFalse();
+ }
+
+ @Test
+ public void doCheckSignalStrength_mPlayerIsNull() {
+ Message msg = new Message();
+ msg.what = TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH;
+ assertThat(tunerSessionWorker.handleMessage(msg)).isFalse();
+ }
+
+ @Test
+ public void handleSignal_isNotUsed() {
+ assertThat(tunerSessionWorker.handleSignal(TvInputConstantCompat.SIGNAL_STRENGTH_NOT_USED))
+ .isTrue();
+ assertThat(mSignalStrength).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_NOT_USED);
+ }
+
+ @Test
+ public void handleSignal_isError() {
+ assertThat(tunerSessionWorker.handleSignal(TvInputConstantCompat.SIGNAL_STRENGTH_ERROR))
+ .isTrue();
+ assertThat(mSignalStrength).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_ERROR);
+ }
+
+ @Test
+ public void handleSignal_isUnknown() {
+ assertThat(tunerSessionWorker.handleSignal(TvInputConstantCompat.SIGNAL_STRENGTH_UNKNOWN))
+ .isTrue();
+ assertThat(mSignalStrength).isEqualTo(TvInputConstantCompat.SIGNAL_STRENGTH_UNKNOWN);
+ }
+
+ @Test
+ public void handleSignal_isNotifySignal() {
+ assertThat(tunerSessionWorker.handleSignal(100)).isTrue();
+ assertThat(mSignalStrength).isEqualTo(100);
+ }
+
+ @Test
+ public void preparePlayback_playerIsNotReady() {
+ Mockito.when(
+ mPlayer.prepare(
+ Mockito.eq(RuntimeEnvironment.application),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any()))
+ .thenReturn(false);
+ tunerSessionWorker.preparePlayback();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_TUNE)).isFalse();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_RETRY_PLAYBACK)).isTrue();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH)).isFalse();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL)).isFalse();
+ }
+
+ @Test
+ @Ignore
+ public void preparePlayback_playerIsReady() {
+ Mockito.when(
+ mPlayer.prepare(
+ RuntimeEnvironment.application,
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any()))
+ .thenReturn(true);
+ tunerSessionWorker.preparePlayback();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_RETRY_PLAYBACK)).isFalse();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH)).isTrue();
+ assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL)).isTrue();
+ }
+}
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/datamanager/ChannelDataManagerTest.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/datamanager/ChannelDataManagerTest.java
new file mode 100644
index 0000000..12f4d23
--- /dev/null
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/datamanager/ChannelDataManagerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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.tuner.tvinput.datamanager;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.content.pm.ProviderInfo;
+import android.media.tv.TvContract;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.android.tv.testing.fakes.FakeTvProvider;
+import com.android.tv.tuner.data.Channel;
+import com.android.tv.tuner.data.TunerChannel;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowContextWrapper;
+
+/** Tests for {@link com.android.tv.tuner.tvinput.datamanager.ChannelDataManager}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
+public class ChannelDataManagerTest {
+
+ private ChannelDataManager mChannelDataManager;
+
+ @Before
+ public void setup() {
+ ProviderInfo info = new ProviderInfo();
+ info.authority = TvContract.AUTHORITY;
+ FakeTvProvider provider =
+ Robolectric.buildContentProvider(FakeTvProvider.class).create(info).get();
+ provider.setCallingPackage("com.android.tv");
+ provider.onCreate();
+ ShadowContextWrapper shadowContextWrapper = new ShadowContextWrapper();
+ shadowContextWrapper.grantPermissions(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS);
+ ShadowContentResolver.registerProviderInternal(TvContract.AUTHORITY, provider);
+ provider.delete(TvContract.Channels.CONTENT_URI, null, null);
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(TvContract.Channels.COLUMN_INPUT_ID, "com.android.tv");
+ contentValues.put(
+ TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA,
+ Channel.TunerChannelProto.getDefaultInstance().toByteArray());
+ contentValues.put(TvContract.Channels.COLUMN_LOCKED, 0);
+ provider.insert(TvContract.Channels.CONTENT_URI, contentValues);
+ contentValues.put(TvContract.Channels.COLUMN_LOCKED, 1);
+ provider.insert(TvContract.Channels.CONTENT_URI, contentValues);
+
+ mChannelDataManager = new ChannelDataManager(RuntimeEnvironment.application, "testInput");
+ }
+
+ @After
+ public void tearDown() {
+ mChannelDataManager.releaseSafely();
+ }
+
+ @Test
+ public void getChannel_locked() {
+ TunerChannel tunerChannel = mChannelDataManager.getChannel(2L);
+ assertThat(tunerChannel.isLocked()).isTrue();
+ }
+
+ @Test
+ public void getChannel_unlocked() {
+ TunerChannel tunerChannel = mChannelDataManager.getChannel(1L);
+ assertThat(tunerChannel.isLocked()).isFalse();
+ }
+}
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/util/PostalCodeUtilsTest.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/util/PostalCodeUtilsTest.java
new file mode 100644
index 0000000..36128c1
--- /dev/null
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/util/PostalCodeUtilsTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.tuner.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.common.util.PostalCodeUtils;
+import com.android.tv.testing.constants.ConfigConstants;
+import java.util.Locale;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link PostalCodeUtils} */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class PostalCodeUtilsTest {
+
+ private static final String[] VALID_POSTCODES_US = {"94043", "94063", "90007"};
+ private static final String[] INVALID_POSTCODES_US = {
+ "", "9404", "ABC", "BCD8", "G777", "BT777", "OXX33", "E1WW", "SW1XX", "E11W", "SW10X",
+ "GIR", "B8", "G77", "BT7", "OX33", "E1W", "SW1X"
+ };
+ private static final String[] VALID_POSTCODES_GB = {
+ "GIR", "B8", "G77", "BT7", "OX33", "E1W", "SW1X", "GIR 0AA", "GIR0AA", "B8 2NE", "PR10BJ"
+ };
+ private static final String[] INVALID_POSTCODES_GB = {
+ "", "9404", "ABC", "BCD8", "G777", "BT777", "OXX33", "E1WW", "SW1XX", "E11W", "SW10X",
+ "94043", "94063", "90007", "B8 ", "OX331D"
+ };
+
+ @Test
+ public void validPostcodesUs() {
+ for (String postcode : VALID_POSTCODES_US) {
+ assertThat(PostalCodeUtils.matches(postcode, Locale.US.getCountry())).isTrue();
+ }
+ }
+
+ @Test
+ public void validPostcodesGb() {
+ for (String postcode : VALID_POSTCODES_GB) {
+ assertThat(PostalCodeUtils.matches(postcode, Locale.UK.getCountry())).isTrue();
+ }
+ }
+
+ @Test
+ public void invalidPostcodesUs() {
+ for (String postcode : INVALID_POSTCODES_US) {
+ assertThat(PostalCodeUtils.matches(postcode, Locale.US.getCountry())).isFalse();
+ }
+ }
+
+ @Test
+ public void invalidPostcodesGb() {
+ for (String postcode : INVALID_POSTCODES_GB) {
+ assertThat(PostalCodeUtils.matches(postcode, Locale.UK.getCountry())).isFalse();
+ }
+ }
+
+ @Test
+ public void unsupportedRegion() {
+ for (String postcode : INVALID_POSTCODES_US) {
+ // {@link Locale.ROOT} is an empty Locale
+ assertThat(PostalCodeUtils.matches(postcode, Locale.ROOT.getCountry())).isTrue();
+ }
+ }
+}
diff --git a/tuner/tests/testing/AndroidManifest.xml b/tuner/tests/testing/AndroidManifest.xml
index 7e07a52..9fcecf9 100644
--- a/tuner/tests/testing/AndroidManifest.xml
+++ b/tuner/tests/testing/AndroidManifest.xml
@@ -18,6 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.tuner.testing"
android:versionCode="1">
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application />
</manifest>
diff --git a/tuner/tests/unittests/javatests/AndroidManifest.xml b/tuner/tests/unittests/javatests/AndroidManifest.xml
index 62caefa..ddbddd0 100644
--- a/tuner/tests/unittests/javatests/AndroidManifest.xml
+++ b/tuner/tests/unittests/javatests/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.tuner.layout.tests" >
- <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="27"/>
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="28"/>
<instrumentation
android:name="android.test.InstrumentationTestRunner"
diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/AndroidManifest.xml b/tuner/tests/unittests/javatests/com/android/tv/tuner/AndroidManifest.xml
index 6fe0b85..6956426 100644
--- a/tuner/tests/unittests/javatests/com/android/tv/tuner/AndroidManifest.xml
+++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/AndroidManifest.xml
@@ -18,11 +18,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.tuner.tests" >
- <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="27" />
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="28" />
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.tv" />
+ android:targetPackage="com.android.tv.tuner.sample.dvb" />
<application android:label="TunerTvInputTests" >
<uses-library android:name="android.test.runner" />
diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTest.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTest.java
index ef653f8..fb9c635 100644
--- a/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTest.java
+++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTest.java
@@ -24,21 +24,29 @@
import android.test.InstrumentationTestCase;
import android.util.Log;
import android.view.Surface;
+
import androidx.test.filters.LargeTest;
+
import com.android.tv.common.flags.impl.DefaultConcurrentDvrPlaybackFlags;
import com.android.tv.tuner.data.Cea708Data;
+import com.android.tv.tuner.data.Channel.AudioStreamType;
+import com.android.tv.tuner.data.Channel.VideoStreamType;
import com.android.tv.tuner.data.PsiData;
import com.android.tv.tuner.data.PsipData;
import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
import com.android.tv.tuner.exoplayer.MpegTsPlayer;
import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager;
import com.android.tv.tuner.source.TsDataSourceManager;
+import com.android.tv.tuner.source.TsDataSourceManager.Factory;
import com.android.tv.tuner.ts.EventDetector.EventListener;
+
import com.google.android.exoplayer.ExoPlayer;
+
+import org.junit.Ignore;
+
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -49,7 +57,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
-import org.junit.Ignore;
/** This class use {@link FileTunerHal} to simulate tunerhal's actions to test zapping time. */
@LargeTest
@@ -99,10 +106,10 @@
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
List<PsiData.PmtItem> pmtItems = new ArrayList<>();
- pmtItems.add(new PsiData.PmtItem(Channel.VideoStreamType.MPEG2, VIDEO_PID, null, null));
+ pmtItems.add(new PsiData.PmtItem(VideoStreamType.MPEG2_VALUE, VIDEO_PID, null, null));
for (int audioPid : AUDIO_PIDS) {
pmtItems.add(
- new PsiData.PmtItem(Channel.AudioStreamType.A52AC3AUDIO, audioPid, null, null));
+ new PsiData.PmtItem(AudioStreamType.A52AC3AUDIO_VALUE, audioPid, null, null));
}
Context context = getInstrumentation().getContext();
@@ -117,7 +124,8 @@
mChannel.setModulation(MODULATION);
mTunerHal = new FileTunerHal(context, tsCacheFile);
mTunerHal.openFirstAvailable();
- mSourceManager = TsDataSourceManager.createSourceManager(false);
+ TsDataSourceManager.Factory tsFactory = new Factory(null);
+ mSourceManager = tsFactory.create(false);
mSourceManager.addTunerHalForTest(mTunerHal);
mHandler =
new Handler(
@@ -155,8 +163,7 @@
new MpegTsRendererBuilder(
mTargetContext,
bufferManager,
- mPlaybackBufferListener,
- mConcurrentDvrPlaybackFlags),
+ mPlaybackBufferListener),
mHandler,
mSourceManager,
null,
diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml
index 77c7f40..79b0987 100644
--- a/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml
+++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tv.tuner"
android:versionCode="1">
- <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="23"/>
<application android:label="TunerTvInputLayoutTests" >
<activity android:name="com.android.tv.tuner.layout.tests.ScaledLayoutActivity"
android:label="ScaledLayout Test" />
diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/AndroidManifest.xml b/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/AndroidManifest.xml
new file mode 100644
index 0000000..19cc0e5
--- /dev/null
+++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tv.tuner.setup.tests" >
+
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="28" />
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tv.tuner.sample.dvb" />
+
+ <application android:label="TunerTvInputTests" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/TunerHalCreatorTest.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/TunerHalCreatorTest.java
index a3a3208..cc5e5c5 100644
--- a/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/TunerHalCreatorTest.java
+++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/TunerHalCreatorTest.java
@@ -21,14 +21,18 @@
import static org.junit.Assert.assertSame;
import android.os.AsyncTask;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+
import com.android.tv.tuner.api.Tuner;
import com.android.tv.tuner.setup.BaseTunerSetupActivity.TunerHalCreator;
-import java.util.concurrent.Executor;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.Executor;
+
/** Tests for {@link TunerHalCreator}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -37,7 +41,7 @@
private static class TestTunerHalCreator extends TunerHalCreator {
private TestTunerHalCreator(Executor executor) {
- super(null, executor);
+ super(null, executor, null);
}
@Override