[automerger skipped] DO NOT MERGE - Merge pi-dev@5234907 into stage-aosp-master
am: c8b794291c -s ours
am skip reason: subject contains skip directive
Change-Id: I65dd67f811e4c464cc8a60cbbb51ea349c544fba
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..4268636
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,96 @@
+//
+// 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.
+//
+
+version_name = "1.20-asop"
+version_code = "417000328"
+
+android_app {
+ name: "LiveTv",
+
+ srcs: ["src/**/*.java"],
+
+ // TODO(b/122608868) turn proguard back on
+ optimize: {
+ enabled: false,
+ },
+
+ // It is required for com.android.providers.tv.permission.ALL_EPG_DATA
+ privileged: true,
+
+ sdk_version: "system_current",
+ min_sdk_version: "23", // M
+
+ resource_dirs: [
+ "res",
+ "material_res",
+
+ ],
+
+ libs: ["tv-guava-android-jar"],
+
+ 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",
+ "jsr330",
+ "live-channels-partner-support",
+ "live-tv-tuner-proto",
+ "live-tv-tuner",
+ "tv-auto-value-jar",
+ "tv-auto-factory-jar",
+ "tv-common",
+ "tv-error-prone-annotations-jar",
+ "tv-lib-dagger",
+ "tv-lib-exoplayer",
+ "tv-lib-exoplayer-v2-core",
+ "tv-lib-dagger-android",
+ ],
+
+ plugins: [
+ "tv-auto-value",
+ "tv-auto-factory",
+ "tv-lib-dagger-android-processor",
+ "tv-lib-dagger-compiler",
+ ],
+
+ javacflags: [
+ "-Xlint:deprecation",
+ "-Xlint:unchecked",
+ ],
+
+ aaptflags: [
+ "--version-name",
+ version_name,
+
+ "--version-code",
+ version_code,
+
+ "--extra-packages",
+ "com.android.tv.tuner",
+
+ "--extra-packages",
+ "com.android.tv.common",
+ ],
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 351d8b5..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,102 +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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-
-LOCAL_MODULE_TAGS := optional
-
-include $(LOCAL_PATH)/version.mk
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := LiveTv
-
-# It is required for com.android.providers.tv.permission.ALL_EPG_DATA
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_SDK_VERSION := system_current
-LOCAL_MIN_SDK_VERSION := 23 # M
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_RESOURCE_DIR := \
- $(LOCAL_PATH)/res \
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-annotations \
- lib-exoplayer \
- lib-exoplayer-v2-core \
- jsr330 \
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-compat \
- android-support-core-ui \
- android-support-tv-provider \
- 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 \
- live-channels-partner-support \
- live-tv-tuner \
- tv-common \
-
-
-LOCAL_JAVACFLAGS := -Xlint:deprecation -Xlint:unchecked
-
-LOCAL_AAPT_FLAGS += \
- --version-name "$(version_name_package)" \
- --version-code $(version_code_package) \
-
-LOCAL_JNI_SHARED_LIBRARIES := libtunertvinput_jni
-LOCAL_AAPT_FLAGS += --extra-packages com.android.tv.tuner
-
-include $(BUILD_PACKAGE)
-
-#############################################################
-# Pre-built dependency jars
-#############################################################
-prebuilts := \
- lib-exoplayer:libs/exoplayer-r1.5.16.aar \
- lib-exoplayer-v2-core:libs/exoplayer-core-2-SNAPHOT-20180114.aar \
- auto-value-jar:../../../prebuilts/tools/common/m2/repository/com/google/auto/value/auto-value/1.5.2/auto-value-1.5.2.jar \
- javax-annotations-jar:../../../prebuilts/tools/common/m2/repository/javax/annotation/javax.annotation-api/1.2/javax.annotation-api-1.2.jar \
- truth-0-36-prebuilt-jar:../../../prebuilts/tools/common/m2/repository/com/google/truth/truth/0.36/truth-0.36.jar \
-
-define define-prebuilt
- $(eval tw := $(subst :, ,$(strip $(1)))) \
- $(eval include $(CLEAR_VARS)) \
- $(eval LOCAL_MODULE := $(word 1,$(tw))) \
- $(eval LOCAL_MODULE_TAGS := optional) \
- $(eval LOCAL_MODULE_CLASS := JAVA_LIBRARIES) \
- $(eval LOCAL_SRC_FILES := $(word 2,$(tw))) \
- $(eval LOCAL_UNINSTALLABLE_MODULE := true) \
- $(eval LOCAL_SDK_VERSION := current) \
- $(eval include $(BUILD_PREBUILT))
-endef
-
-$(foreach p,$(prebuilts),\
- $(call define-prebuilt,$(p)))
-
-prebuilts :=
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3456f16..a398823 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -14,17 +14,19 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+<!-- This manifest is for LiveTv -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.tv" >
<uses-sdk
android:minSdkVersion="23"
- android:targetSdkVersion="26" />
+ android:targetSdkVersion="27" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE" />
- <uses-permission android:name="android.permission.GLOBAL_SEARCH" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.HDMI_CEC" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_PARENTAL_CONTROLS" />
@@ -32,6 +34,7 @@
<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="android.permission.WRITE_EXTERNAL_STORAGE" />
<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.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
@@ -70,22 +73,14 @@
<application
android:name="com.android.tv.app.LiveTvApplication"
android:allowBackup="true"
- android:banner="@drawable/banner"
- android:icon="@drawable/ic_live_channels"
+ android:appComponentFactory="android.support.v4.app.CoreComponentFactory"
+ android:banner="@drawable/live_tv_banner"
+ android:icon="@drawable/ic_tv_app"
android:label="@string/app_name"
android:supportsRtl="true"
- android:theme="@style/Theme.TV" >
- <activity
- android:name="com.android.tv.tuner.setup.LiveTvTunerSetupActivity"
- android:configChanges="keyboard|keyboardHidden"
- android:label="@string/bt_app_name"
- android:launchMode="singleInstance"
- android:process="com.android.tv.tuner"
- android:theme="@style/Theme.Setup.GuidedStep" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity>
+ android:theme="@style/Theme.TV"
+ tools:replace="android:appComponentFactory">
+ >
<!-- providers are listed here to keep them separate from the internal versions -->
<provider
@@ -103,7 +98,32 @@
android:exported="false"
android:process="com.android.tv.common" />
- <activity android:name="com.android.tv.TvActivity" >
+
+
+ <receiver
+ android:name="com.android.tv.livetv.receiver.GlobalKeyReceiver"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.GLOBAL_BUTTON" />
+ </intent-filter>
+
+ <!--
+ Not directly related to GlobalKeyReceiver but needed to be able to provide our
+ content rating definitions to the system service.
+ -->
+ <intent-filter>
+ <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
+ android:resource="@xml/tv_content_rating_systems" />
+ </receiver>
+
+ <activity
+ android:name="com.android.tv.TvActivity"
+ android:exported="true"
+ android:launchMode="singleTask" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -202,8 +222,9 @@
</intent-filter>
</activity>
<activity
- android:name="com.android.tv.dvr.ui.browse.DvrDetailsActivity"
+ android:name="com.android.tv.ui.DetailsActivity"
android:configChanges="keyboard|keyboardHidden"
+ android:exported="true"
android:theme="@style/Theme.TV.Dvr.Browse.Details" />
<activity
android:name="com.android.tv.dvr.ui.DvrSeriesSettingsActivity"
@@ -243,6 +264,7 @@
<action android:name="android.intent.action.PACKAGE_ADDED" />
<!-- PACKAGE_CHANGED for package enabled/disabled notification -->
<action android:name="android.intent.action.PACKAGE_CHANGED" />
+ <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
@@ -255,7 +277,7 @@
android:name="com.android.tv.setup.SystemSetupActivity"
android:configChanges="keyboard|keyboardHidden"
android:exported="true"
- android:label="@string/bt_app_name"
+ android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@style/Theme.Setup.GuidedStep" >
<intent-filter>
@@ -263,23 +285,7 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
- </activity>
- <!--
- TunerInputController should be the same process with MainActivity to check status
- of MainActivity
- -->
- <receiver
- android:name="com.android.tv.tuner.TunerInputController$IntentReceiver"
- android:exported="false" >
- <intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED" />
- <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
- <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
- <action android:name="com.android.tv.action.APPLICATION_FIRST_LAUNCHED" />
- <action android:name="com.android.tv.action.NETWORK_TUNER_ATTACHED" />
- <action android:name="com.android.tv.action.NETWORK_TUNER_DETACHED" />
- </intent-filter>
- </receiver> <!-- DVR -->
+ </activity> <!-- DVR -->
<service
android:name="com.android.tv.dvr.recorder.DvrRecordingService"
android:label="@string/dvr_service_name" />
@@ -287,48 +293,8 @@
<receiver android:name="com.android.tv.dvr.recorder.DvrStartRecordingReceiver" />
<service
- android:name="com.android.tv.tuner.tvinput.TunerStorageCleanUpService"
- android:exported="false"
- android:permission="android.permission.BIND_JOB_SERVICE"
- android:process="com.android.tv.tuner" />
- <service
android:name="com.android.tv.data.epg.EpgFetchService"
android:permission="android.permission.BIND_JOB_SERVICE" />
-
- <receiver
- android:name="com.android.tv.livetv.receiver.GlobalKeyReceiver"
- android:exported="true" >
- <intent-filter>
- <action android:name="android.intent.action.GLOBAL_BUTTON" />
- </intent-filter>
-
- <!--
- Not directly related to GlobalKeyReceiver but needed to be able to provide our
- content rating definitions to the system service.
- -->
- <intent-filter>
- <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
- </intent-filter>
-
- <meta-data
- android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
- android:resource="@xml/tv_content_rating_systems" />
- </receiver>
-
- <service
- android:name="com.android.tv.tuner.livetuner.LiveTvTunerTvInputService"
- android:enabled="false"
- android:label="@string/bt_app_name"
- android:permission="android.permission.BIND_TV_INPUT"
- android:process="com.android.tv.tuner" >
- <intent-filter>
- <action android:name="android.media.tv.TvInputService" />
- </intent-filter>
-
- <meta-data
- android:name="android.media.tv.input"
- android:resource="@xml/ut_tvinputservice" />
- </service>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..63c1f44
--- /dev/null
+++ b/README.md
@@ -0,0 +1,36 @@
+# Live TV
+
+__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
+
+```bash
+echo "Compiling"
+m -j LiveTv
+echo "Installing"
+adb install -r ${OUT}/system/priv-app/LiveTv/LiveTv.apk
+
+```
+
+If it is your first time installing LiveTv you will need to do
+
+```bash
+adb root
+adb remount
+adb push ${OUT}/system/priv-app/LiveTv/LiveTv.apk /system/priv-app/LiveTv/LiveTv.apk
+adb reboot
+```
\ No newline at end of file
diff --git a/ResourceManifest.xml b/ResourceManifest.xml
deleted file mode 100644
index a859327..0000000
--- a/ResourceManifest.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tv" xmlns:tools="http://schemas.android.com/tools">
- <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/>
- <application />
-</manifest>
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..23e3dbd
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+
+/*
+ * Experimental gradle configuration. This file may not be up to date.
+ */
+
+buildscript {
+ repositories {
+ mavenCentral()
+ google()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.1.4'
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
+ }
+}
+apply plugin: 'com.android.application'
+android {
+ compileSdkVersion 28
+ buildToolsVersion '28.0.3'
+ dexOptions {
+ preDexLibraries = false
+ additionalParameters=['--core-library']
+ javaMaxHeapSize "6g"
+ }
+ android {
+ defaultConfig {
+ resConfigs "en"
+ }
+ }
+ defaultConfig {
+ minSdkVersion 23
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ debug {
+ minifyEnabled false
+ }
+ release {
+ minifyEnabled true
+ }
+ }
+ compileOptions() {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ sourceSets {
+ main {
+ res.srcDirs = ['res', 'material_res']
+ java.srcDirs = ['src', 'partner_support/src']
+ manifest.srcFile 'AndroidManifest.xml'
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+ jcenter()
+ 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.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'
+
+ /*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
diff --git a/common/Android.bp b/common/Android.bp
new file mode 100644
index 0000000..63759d4
--- /dev/null
+++ b/common/Android.bp
@@ -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.
+
+android_library {
+ name: "tv-common",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.proto",
+ ],
+
+ sdk_version: "system_current",
+
+ proto: {
+ type: "lite",
+ },
+
+ resource_dirs: ["res"],
+
+ libs: [
+ "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",
+ ],
+
+ static_libs: ["tv-lib-dagger-android"],
+
+ plugins: [
+ "tv-auto-value",
+ "tv-auto-factory",
+ "tv-lib-dagger-android-processor",
+ "tv-lib-dagger-compiler",
+ ],
+
+
+ min_sdk_version: "23",
+
+ // TODO(b/77284273): generate build config after dagger supports libraries
+ //include $(LOCAL_PATH)/buildconfig.mk
+
+}
diff --git a/common/Android.mk b/common/Android.mk
deleted file mode 100644
index 48f969e..0000000
--- a/common/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include all common java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := tv-common
-LOCAL_MODULE_CLASS := STATIC_JAVA_LIBRARIES
-LOCAL_MODULE_TAGS := optional
-LOCAL_SDK_VERSION := system_current
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_JAVA_LIBRARIES := \
- android-support-annotations
-
-LOCAL_DISABLE_RESOLVE_SUPPORT_LIBRARIES := true
-
-LOCAL_SHARED_ANDROID_LIBRARIES := \
- android-support-compat \
- android-support-core-ui \
- android-support-v7-recyclerview \
- android-support-v17-leanback
-
-LOCAL_MIN_SDK_VERSION := 23
-
-include $(LOCAL_PATH)/buildconfig.mk
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/AndroidManifest.xml b/common/AndroidManifest.xml
index c1c698c..7002d5f 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="26" android:minSdkVersion="21"/>
+ <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
<application />
</manifest>
diff --git a/common/build.gradle b/common/build.gradle
new file mode 100644
index 0000000..f371475
--- /dev/null
+++ b/common/build.gradle
@@ -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.
+ */
+
+
+/*
+ * Experimental gradle configuration. This file may not be up to date.
+ */
+
+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"
+ }
+ }
+
+ defaultConfig {
+ minSdkVersion 23
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ debug {
+ minifyEnabled false
+ buildConfigField "boolean", "AOSP", "true"
+ buildConfigField "boolean", "ENG", "true"
+ buildConfigField "boolean", "NO_JNI_TEST", "false"
+ }
+ release {
+ minifyEnabled true
+ buildConfigField "boolean", "AOSP", "true"
+ buildConfigField "boolean", "ENG", "false"
+ buildConfigField "boolean", "NO_JNI_TEST", "false"
+ }
+ }
+ compileOptions() {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ sourceSets {
+ main {
+ res.srcDirs = ['res']
+ java.srcDirs = ['src']
+ manifest.srcFile 'AndroidManifest.xml'
+ proto {
+ srcDir 'src/com/android/tv/common/compat/internal'
+ }
+ }
+ }
+}
+
+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'
+}
+protobuf {
+ // Configure the protoc executable
+ protoc {
+ 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
+ }
+ task.plugins {
+ javalite {}
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/com/android/tv/common/BaseApplication.java b/common/src/com/android/tv/common/BaseApplication.java
index 71c9b4d..45c3256 100644
--- a/common/src/com/android/tv/common/BaseApplication.java
+++ b/common/src/com/android/tv/common/BaseApplication.java
@@ -17,10 +17,7 @@
package com.android.tv.common;
import android.annotation.TargetApi;
-import android.app.Application;
import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.StrictMode;
import android.support.annotation.VisibleForTesting;
@@ -30,9 +27,10 @@
import com.android.tv.common.util.CommonUtils;
import com.android.tv.common.util.Debug;
import com.android.tv.common.util.SystemProperties;
+import dagger.android.DaggerApplication;
/** The base application class for Live TV applications. */
-public abstract class BaseApplication extends Application implements BaseSingletons {
+public abstract class BaseApplication extends DaggerApplication implements BaseSingletons {
private RecordingStorageStatusManager mRecordingStorageStatusManager;
/**
@@ -41,7 +39,13 @@
*/
@VisibleForTesting public static BaseSingletons sSingletons;
- /** Returns the {@link BaseSingletons} using the application context. */
+ /**
+ * Returns the {@link BaseSingletons} using the application context.
+ *
+ * @deprecated use {@link com.android.tv.common.singletons.HasSingletons#get(Class, Context)}
+ * instead
+ */
+ @Deprecated
public static BaseSingletons getSingletons(Context context) {
// STOP-SHIP: changing the method to protected once the Tuner application is created.
// No need to be "synchronized" because this doesn't create any instance.
@@ -65,8 +69,15 @@
StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog();
// TODO(b/69565157): Turn penaltyDeath on for VMPolicy when tests are fixed.
+ // TODO(b/120840665): Restore detecting untagged network sockets
StrictMode.VmPolicy.Builder vmPolicyBuilder =
- new StrictMode.VmPolicy.Builder().detectAll().penaltyLog();
+ new StrictMode.VmPolicy.Builder()
+ .detectActivityLeaks()
+ .detectLeakedClosableObjects()
+ .detectLeakedRegistrationObjects()
+ .detectFileUriExposure()
+ .detectContentUriWithoutPermission()
+ .penaltyLog();
if (!CommonUtils.isRunningInTest()) {
threadPolicyBuilder.penaltyDialog();
@@ -77,14 +88,6 @@
if (CommonFeatures.DVR.isEnabled(this)) {
getRecordingStorageStatusManager();
}
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- // Fetch remote config
- getRemoteConfig().fetch(null);
- return null;
- }
- }.execute();
}
@Override
@@ -101,7 +104,4 @@
}
return mRecordingStorageStatusManager;
}
-
- @Override
- public abstract Intent getTunerSetupIntent(Context context);
}
diff --git a/common/src/com/android/tv/common/BaseSingletons.java b/common/src/com/android/tv/common/BaseSingletons.java
index e735cdb..1053061 100644
--- a/common/src/com/android/tv/common/BaseSingletons.java
+++ b/common/src/com/android/tv/common/BaseSingletons.java
@@ -16,20 +16,17 @@
package com.android.tv.common;
-import android.content.Context;
-import android.content.Intent;
-import com.android.tv.common.config.api.RemoteConfig.HasRemoteConfig;
+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 HasRemoteConfig {
+public interface BaseSingletons
+ extends HasCloudEpgFlags, HasBuildType, HasConcurrentDvrPlaybackFlags {
Clock getClock();
RecordingStorageStatusManager getRecordingStorageStatusManager();
-
- Intent getTunerSetupIntent(Context context);
-
- String getEmbeddedTunerInputId();
}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/BuildConfig.java
similarity index 60%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/BuildConfig.java
index 3e24a49..b3ad002 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/BuildConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv.common;
-package com.android.tv.util;
+/* Hard coded BuildConfig. */
+public final class BuildConfig {
+ public static final boolean DEBUG = true;
+ public static final boolean ENG = false;
+ public static final boolean NO_JNI_TEST = false;
+ public static final boolean AOSP = true;
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
-}
+ private BuildConfig() {}
+}
\ No newline at end of file
diff --git a/common/src/com/android/tv/common/CommonConstants.java b/common/src/com/android/tv/common/CommonConstants.java
index ac379d1..43d9851 100644
--- a/common/src/com/android/tv/common/CommonConstants.java
+++ b/common/src/com/android/tv/common/CommonConstants.java
@@ -19,10 +19,20 @@
/** Constants for common use in apps and tests. */
public final class CommonConstants {
+ @Deprecated // TODO(b/121158110) refactor so this is not needed.
public static final String BASE_PACKAGE =
+// AOSP_Comment_Out !BuildConfig.AOSP
+// AOSP_Comment_Out ? "com.google.android.tv"
+// AOSP_Comment_Out :
"com.android.tv";
/** A constant for the key of the extra data for the app linking intent. */
public static final String EXTRA_APP_LINK_CHANNEL_URI = "app_link_channel_uri";
+ /**
+ * Video is unavailable because the source is not physically connected, for example the HDMI
+ * cable is not connected.
+ */
+ public static final int VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 5;
+
private CommonConstants() {}
}
diff --git a/common/src/com/android/tv/common/TvContentRatingCache.java b/common/src/com/android/tv/common/TvContentRatingCache.java
index cfdb8e4..f2fda69 100644
--- a/common/src/com/android/tv/common/TvContentRatingCache.java
+++ b/common/src/com/android/tv/common/TvContentRatingCache.java
@@ -23,9 +23,8 @@
import android.util.ArrayMap;
import android.util.Log;
import com.android.tv.common.memory.MemoryManageable;
-import java.util.ArrayList;
+import com.google.common.collect.ImmutableList;
import java.util.Collections;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
@@ -42,19 +41,19 @@
}
// @GuardedBy("TvContentRatingCache.this")
- private final Map<String, TvContentRating[]> mRatingsMultiMap = new ArrayMap<>();
+ private final Map<String, ImmutableList<TvContentRating>> mRatingsMultiMap = new ArrayMap<>();
/**
* Returns an array TvContentRatings from a string of comma separated set of rating strings
- * creating each from {@link TvContentRating#unflattenFromString(String)} if needed. Returns
- * {@code null} if the string is empty or contains no valid ratings.
+ * creating each from {@link TvContentRating#unflattenFromString(String)} if needed or an empty
+ * list if the string is empty or contains no valid ratings.
*/
- @Nullable
- public synchronized TvContentRating[] getRatings(String commaSeparatedRatings) {
+ public synchronized ImmutableList<TvContentRating> getRatings(
+ @Nullable String commaSeparatedRatings) {
if (TextUtils.isEmpty(commaSeparatedRatings)) {
- return null;
+ return ImmutableList.of();
}
- TvContentRating[] tvContentRatings;
+ ImmutableList<TvContentRating> tvContentRatings;
if (mRatingsMultiMap.containsKey(commaSeparatedRatings)) {
tvContentRatings = mRatingsMultiMap.get(commaSeparatedRatings);
} else {
@@ -76,12 +75,13 @@
/** Returns a sorted array of TvContentRatings from a comma separated string of ratings. */
@VisibleForTesting
- static TvContentRating[] stringToContentRatings(String commaSeparatedRatings) {
+ static ImmutableList<TvContentRating> stringToContentRatings(
+ @Nullable String commaSeparatedRatings) {
if (TextUtils.isEmpty(commaSeparatedRatings)) {
- return null;
+ return ImmutableList.of();
}
Set<String> ratingStrings = getSortedSetFromCsv(commaSeparatedRatings);
- List<TvContentRating> contentRatings = new ArrayList<>();
+ ImmutableList.Builder<TvContentRating> contentRatings = ImmutableList.builder();
for (String rating : ratingStrings) {
try {
contentRatings.add(TvContentRating.unflattenFromString(rating));
@@ -89,9 +89,7 @@
Log.e(TAG, "Can't parse the content rating: '" + rating + "'", e);
}
}
- return contentRatings.size() == 0
- ? null
- : contentRatings.toArray(new TvContentRating[contentRatings.size()]);
+ return contentRatings.build();
}
private static Set<String> getSortedSetFromCsv(String commaSeparatedRatings) {
@@ -118,19 +116,17 @@
* Returns a string of each flattened content rating, sorted and concatenated together with a
* comma.
*/
- public static String contentRatingsToString(TvContentRating[] contentRatings) {
- if (contentRatings == null || contentRatings.length == 0) {
+ @Nullable
+ public static String contentRatingsToString(
+ @Nullable ImmutableList<TvContentRating> contentRatings) {
+ if (contentRatings == null) {
return null;
}
- String[] ratingStrings = new String[contentRatings.length];
- for (int i = 0; i < contentRatings.length; i++) {
- ratingStrings[i] = contentRatings[i].flattenToString();
+ SortedSet<String> ratingStrings = new TreeSet<>();
+ for (TvContentRating rating : contentRatings) {
+ ratingStrings.add(rating.flattenToString());
}
- if (ratingStrings.length == 1) {
- return ratingStrings[0];
- } else {
- return TextUtils.join(",", toSortedSet(ratingStrings));
- }
+ return TextUtils.join(",", ratingStrings);
}
@Override
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/buildtype/AospBuildTypeProvider.java
similarity index 65%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/buildtype/AospBuildTypeProvider.java
index 3e24a49..8d39b3a 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/buildtype/AospBuildTypeProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv.common.buildtype;
-package com.android.tv.util;
+/** {@code AOSP} {@link HasBuildType}. */
+public class AospBuildTypeProvider implements HasBuildType {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ @Override
+ public BuildType getBuildType() {
+ return BuildType.AOSP;
+ }
}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/buildtype/EngBuildTypeProvider.java
similarity index 66%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/buildtype/EngBuildTypeProvider.java
index 3e24a49..5f18794 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/buildtype/EngBuildTypeProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv.common.buildtype;
-package com.android.tv.util;
+/** {@code ENG} {@link HasBuildType}. */
+public class EngBuildTypeProvider implements HasBuildType {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ @Override
+ public BuildType getBuildType() {
+ return BuildType.ENG;
+ }
}
diff --git a/common/src/com/android/tv/common/buildtype/HasBuildType.java b/common/src/com/android/tv/common/buildtype/HasBuildType.java
new file mode 100644
index 0000000..7d5677c
--- /dev/null
+++ b/common/src/com/android/tv/common/buildtype/HasBuildType.java
@@ -0,0 +1,34 @@
+/*
+ * 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.buildtype;
+
+/**
+ * The provides a {@link BuildType} for selecting features in code.
+ *
+ * <p>This is considered an anti-pattern and new usages should be discouraged.
+ */
+public interface HasBuildType {
+
+ /** Compile time constant for build type. */
+ enum BuildType {
+ AOSP,
+ ENG,
+ NO_JNI_TEST,
+ PROD
+ }
+
+ BuildType getBuildType();
+}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/buildtype/NoJniTestBuildTypeProvider.java
similarity index 64%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/buildtype/NoJniTestBuildTypeProvider.java
index 3e24a49..1620af2 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/buildtype/NoJniTestBuildTypeProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv.common.buildtype;
-package com.android.tv.util;
+/** {@code NO_JNI_TEST} {@link HasBuildType}. */
+public class NoJniTestBuildTypeProvider implements HasBuildType {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ @Override
+ public BuildType getBuildType() {
+ return BuildType.NO_JNI_TEST;
+ }
}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/buildtype/ProdBuildTypeProvider.java
similarity index 65%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/buildtype/ProdBuildTypeProvider.java
index 3e24a49..16db326 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/buildtype/ProdBuildTypeProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv.common.buildtype;
-package com.android.tv.util;
+/** {@code Prod} {@link HasBuildType}. */
+public class ProdBuildTypeProvider implements HasBuildType {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ @Override
+ public BuildType getBuildType() {
+ return BuildType.PROD;
+ }
}
diff --git a/common/src/com/android/tv/common/compat/README.md b/common/src/com/android/tv/common/compat/README.md
new file mode 100644
index 0000000..8d87d83
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/README.md
@@ -0,0 +1,7 @@
+# TIF Compatibility Library
+
+This is the temporary location for the of Compatibility Library while it is under development.
+It will eventually move to a support library location.
+
+
+See go/tif-compat-proposal
\ No newline at end of file
diff --git a/common/src/com/android/tv/common/compat/RecordingSessionCompat.java b/common/src/com/android/tv/common/compat/RecordingSessionCompat.java
new file mode 100644
index 0000000..6941e47
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/RecordingSessionCompat.java
@@ -0,0 +1,69 @@
+/*
+ * 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 android.content.Context;
+import android.media.tv.TvInputService.RecordingSession;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.support.annotation.RequiresApi;
+import com.android.tv.common.compat.api.RecordingSessionCompatCommands;
+import com.android.tv.common.compat.api.RecordingSessionCompatEvents;
+import com.android.tv.common.compat.api.SessionEventNotifier;
+import com.android.tv.common.compat.internal.RecordingSessionCompatProcessor;
+
+/**
+ * TIF Compatibility for {@link RecordingSession}.
+ *
+ * <p>Extends {@code RecordingSession} in a backwards compatible way.
+ */
+@RequiresApi(api = VERSION_CODES.N)
+public abstract class RecordingSessionCompat extends RecordingSession
+ implements SessionEventNotifier,
+ RecordingSessionCompatCommands,
+ RecordingSessionCompatEvents {
+
+ private final RecordingSessionCompatProcessor mProcessor;
+
+ public RecordingSessionCompat(Context context) {
+ super(context);
+ mProcessor = new RecordingSessionCompatProcessor(this, this);
+ }
+
+ @Override
+ public void onAppPrivateCommand(String action, Bundle data) {
+ if (!mProcessor.handleAppPrivateCommand(action, data)) {
+ super.onAppPrivateCommand(action, data);
+ }
+ }
+
+ /** Display a debug message to the session for display on dev builds only */
+ @Override
+ public void onDevMessage(String message) {}
+
+ /** Notify the client to Display a message in the application as a toast on dev builds only. */
+ @Override
+ public void notifyDevToast(String message) {
+ mProcessor.notifyDevToast(message);
+ }
+
+ /** Notify the client Recording started. */
+ @Override
+ public void notifyRecordingStarted(String uri) {
+ mProcessor.notifyRecordingStarted(uri);
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/TisSessionCompat.java b/common/src/com/android/tv/common/compat/TisSessionCompat.java
new file mode 100644
index 0000000..97f4fb3
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/TisSessionCompat.java
@@ -0,0 +1,77 @@
+/*
+ * 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 android.content.Context;
+import android.media.tv.TvInputService.Session;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.support.annotation.RequiresApi;
+import com.android.tv.common.compat.api.SessionCompatCommands;
+import com.android.tv.common.compat.api.SessionCompatEvents;
+import com.android.tv.common.compat.api.SessionEventNotifier;
+import com.android.tv.common.compat.internal.TifSessionCompatProcessor;
+
+/**
+ * TIF Compatibility for {@link Session}.
+ *
+ * <p>Extends {@code Session} in a backwards compatible way.
+ */
+@RequiresApi(api = VERSION_CODES.LOLLIPOP)
+public abstract class TisSessionCompat extends Session
+ implements SessionEventNotifier, SessionCompatCommands, SessionCompatEvents {
+
+ private final TifSessionCompatProcessor mTifCompatProcessor;
+
+ public TisSessionCompat(Context context) {
+ super(context);
+ mTifCompatProcessor = new TifSessionCompatProcessor(this, this);
+ }
+
+ @Override
+ public void onAppPrivateCommand(String action, Bundle data) {
+ if (!mTifCompatProcessor.handleAppPrivateCommand(action, data)) {
+ super.onAppPrivateCommand(action, data);
+ }
+ }
+
+ @Override
+ public void onDevMessage(String message) {}
+
+ @Override
+ public void notifyDevToast(String message) {
+ mTifCompatProcessor.notifyDevToast(message);
+ }
+
+ /**
+ * Notify the application with current signal strength.
+ *
+ * <p>At each {MainActivity#tune(boolean)}, the signal strength is implicitly reset to {@link
+ * TvInputConstantCompat#SIGNAL_STRENGTH_NOT_USED}. If a TV input supports reporting signal
+ * strength, it should set the signal strength to {@link
+ * TvInputConstantCompat#SIGNAL_STRENGTH_UNKNOWN} in
+ * {TunerSessionWorker#prepareTune(TunerChannel, String)}, until a valid strength is available.
+ *
+ * @param value The current signal strength. Valid values are {@link
+ * TvInputConstantCompat#SIGNAL_STRENGTH_NOT_USED}, {@link
+ * TvInputConstantCompat#SIGNAL_STRENGTH_UNKNOWN}, and 0 - 100 inclusive.
+ */
+ @Override
+ public void notifySignalStrength(int value) {
+ mTifCompatProcessor.notifySignalStrength(value);
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/TvInputConstantCompat.java b/common/src/com/android/tv/common/compat/TvInputConstantCompat.java
new file mode 100644
index 0000000..251e848
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/TvInputConstantCompat.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.compat;
+
+/** Temp TIF Compatibility for {@link TvInputManager} constants. */
+public class TvInputConstantCompat {
+
+ /**
+ * Status for {@link TisSessionCompat#notifySignalStrength(int)} and
+ * {@link TvViewCompat.TvInputCallback#onTimeShiftStatusChanged(String, int)}:
+ *
+ * <p>SIGNAL_STRENGTH_NOT_USED means the TV Input does not report signal strength. Each onTune
+ * command implicitly resets the TV App's signal strength state to SIGNAL_STRENGTH_NOT_USED.
+ */
+ public static final int SIGNAL_STRENGTH_NOT_USED = -3;
+
+ /**
+ * Status for {@link TisSessionCompat#notifySignalStrength(int)} and
+ * {@link TvViewCompat.TvInputCallback#onTimeShiftStatusChanged(String, int)}:
+ *
+ * <p>SIGNAL_STRENGTH_ERROR means exception/error when handling signal strength.
+ */
+ public static final int SIGNAL_STRENGTH_ERROR = -2;
+
+ /**
+ * Status for {@link TisSessionCompat#notifySignalStrength(int)} and
+ * {@link TvViewCompat.TvInputCallback#onTimeShiftStatusChanged(String, int)}:
+ *
+ * <p>SIGNAL_STRENGTH_UNKNOWN means the TV Input supports signal strength, but does not
+ * currently know what the strength is.
+ */
+ public static final int SIGNAL_STRENGTH_UNKNOWN = -1;
+}
diff --git a/common/src/com/android/tv/common/compat/TvInputInfoCompat.java b/common/src/com/android/tv/common/compat/TvInputInfoCompat.java
new file mode 100644
index 0000000..685a3ed
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/TvInputInfoCompat.java
@@ -0,0 +1,117 @@
+/*
+ * 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 android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputService;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * TIF Compatibility for {@link TvInputInfo}.
+ */
+public class TvInputInfoCompat {
+ private static final String TAG = "TvInputInfoCompat";
+ private static final String ATTRIBUTE_NAMESPACE_ANDROID =
+ "http://schemas.android.com/apk/res/android";
+ private static final String TV_INPUT_XML_START_TAG_NAME = "tv-input";
+ private static final String TV_INPUT_EXTRA_XML_START_TAG_NAME = "extra";
+ private static final String ATTRIBUTE_NAME = "name";
+ private static final String ATTRIBUTE_VALUE = "value";
+ private static final String ATTRIBUTE_NAME_AUDIO_ONLY =
+ "com.android.tv.common.compat.tvinputinfocompat.audioOnly";
+
+ private final Context mContext;
+ private final TvInputInfo mTvInputInfo;
+ private final boolean mAudioOnly;
+
+ 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() {
+ return mTvInputInfo;
+ }
+
+ public boolean isAudioOnly() {
+ return mAudioOnly;
+ }
+
+ public int getType() {
+ return mTvInputInfo.getType();
+ }
+
+ @VisibleForTesting
+ public Map<String, String> getExtras() {
+ ServiceInfo si = mTvInputInfo.getServiceInfo();
+
+ try {
+ XmlPullParser parser = getXmlResourceParser();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ if (!TV_INPUT_XML_START_TAG_NAME.equals(parser.getName())) {
+ Log.w(TAG, "Meta-data does not start with " + TV_INPUT_XML_START_TAG_NAME
+ + " tag for " + si.name);
+ return Collections.emptyMap();
+ }
+ // <tv-input> start tag found
+ Map<String, String> extras = new HashMap<>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.END_TAG
+ && TV_INPUT_XML_START_TAG_NAME.equals(parser.getName())) {
+ // </tv-input> end tag found
+ return extras;
+ }
+ if (type == XmlPullParser.START_TAG
+ && TV_INPUT_EXTRA_XML_START_TAG_NAME.equals(parser.getName())) {
+ String extraName =
+ parser.getAttributeValue(ATTRIBUTE_NAMESPACE_ANDROID, ATTRIBUTE_NAME);
+ String extraValue =
+ parser.getAttributeValue(ATTRIBUTE_NAMESPACE_ANDROID, ATTRIBUTE_VALUE);
+ if (extraName != null && extraValue != null) {
+ extras.put(extraName, extraValue);
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get extras of " + mTvInputInfo.getId() , e);
+ }
+ return Collections.emptyMap();
+ }
+
+ @VisibleForTesting
+ XmlPullParser getXmlResourceParser() {
+ ServiceInfo si = mTvInputInfo.getServiceInfo();
+ PackageManager pm = mContext.getPackageManager();
+ return si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA);
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/TvRecordingClientCompat.java b/common/src/com/android/tv/common/compat/TvRecordingClientCompat.java
new file mode 100644
index 0000000..143ff25
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/TvRecordingClientCompat.java
@@ -0,0 +1,101 @@
+/*
+ * 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 android.content.Context;
+import android.media.tv.TvRecordingClient;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.RequiresApi;
+import android.util.ArrayMap;
+import com.android.tv.common.compat.api.PrivateCommandSender;
+import com.android.tv.common.compat.api.RecordingClientCallbackCompatEvents;
+import com.android.tv.common.compat.api.TvRecordingClientCompatCommands;
+import com.android.tv.common.compat.internal.RecordingClientCompatProcessor;
+
+/**
+ * TIF Compatibility for {@link TvRecordingClient}.
+ *
+ * <p>Extends {@code TvRecordingClient} in a backwards compatible way.
+ */
+@RequiresApi(api = VERSION_CODES.N)
+public class TvRecordingClientCompat extends TvRecordingClient
+ implements TvRecordingClientCompatCommands, PrivateCommandSender {
+
+ private final RecordingClientCompatProcessor mProcessor;
+
+ /**
+ * Creates a new TvRecordingClient object.
+ *
+ * @param context The application context to create a TvRecordingClient with.
+ * @param tag A short name for debugging purposes.
+ * @param callback The callback to receive recording status changes.
+ * @param handler The handler to invoke the callback on.
+ */
+ public TvRecordingClientCompat(
+ Context context, String tag, RecordingCallback callback, Handler handler) {
+ super(context, tag, callback, handler);
+ RecordingCallbackCompat compatEvents =
+ callback instanceof RecordingCallbackCompat
+ ? (RecordingCallbackCompat) callback
+ : null;
+ mProcessor = new RecordingClientCompatProcessor(this, compatEvents);
+ if (compatEvents != null) {
+ compatEvents.mClientCompatProcessor = mProcessor;
+ }
+ }
+
+ /** Tell the session to Display a debug message dev builds only. */
+ @Override
+ public void devMessage(String message) {
+ mProcessor.devMessage(message);
+ }
+
+ /**
+ * TIF Compatibility for {@link RecordingCallback}.
+ *
+ * <p>Extends {@code RecordingCallback} in a backwards compatible way.
+ */
+ public static class RecordingCallbackCompat extends RecordingCallback
+ implements RecordingClientCallbackCompatEvents {
+ private final ArrayMap<String, Integer> inputCompatVersionMap = new ArrayMap<>();
+ private RecordingClientCompatProcessor mClientCompatProcessor;
+
+ @Override
+ public void onEvent(String inputId, String eventType, Bundle eventArgs) {
+ if (mClientCompatProcessor != null
+ && !mClientCompatProcessor.handleEvent(inputId, eventType, eventArgs)) {
+ super.onEvent(inputId, eventType, eventArgs);
+ }
+ }
+
+ public int getTifCompatVersionForInput(String inputId) {
+ return inputCompatVersionMap.containsKey(inputId)
+ ? inputCompatVersionMap.get(inputId)
+ : 0;
+ }
+
+ /** Display a message as a toast on dev builds only. */
+ @Override
+ public void onDevToast(String inputId, String message) {}
+
+ /** Recording started. */
+ @Override
+ public void onRecordingStarted(String inputId, String recUri) {}
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/TvViewCompat.java b/common/src/com/android/tv/common/compat/TvViewCompat.java
new file mode 100644
index 0000000..f44564d
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/TvViewCompat.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.content.Context;
+import android.media.tv.TvView;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.support.annotation.RequiresApi;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import com.android.tv.common.compat.api.PrivateCommandSender;
+import com.android.tv.common.compat.api.TvInputCallbackCompatEvents;
+import com.android.tv.common.compat.api.TvViewCompatCommands;
+import com.android.tv.common.compat.internal.TvViewCompatProcessor;
+
+/**
+ * TIF Compatibility for {@link TvView}.
+ *
+ * <p>Extends {@code TvView} in a backwards compatible way.
+ */
+@RequiresApi(api = VERSION_CODES.LOLLIPOP)
+public class TvViewCompat extends TvView implements TvViewCompatCommands, PrivateCommandSender {
+
+ private final TvViewCompatProcessor mTvViewCompatProcessor;
+
+ public TvViewCompat(Context context) {
+ this(context, null);
+ }
+
+ public TvViewCompat(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TvViewCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mTvViewCompatProcessor = new TvViewCompatProcessor(this);
+ }
+
+ @Override
+ public void setCallback(TvInputCallback callback) {
+ super.setCallback(callback);
+ if (callback instanceof TvInputCallbackCompat) {
+ TvInputCallbackCompat compatEvents = (TvInputCallbackCompat) callback;
+ mTvViewCompatProcessor.setCallback(compatEvents);
+ compatEvents.mTvViewCompatProcessor = mTvViewCompatProcessor;
+ }
+ }
+
+ @Override
+ public void devMessage(String message) {
+ mTvViewCompatProcessor.devMessage(message);
+ }
+
+ /**
+ * TIF Compatibility for {@link TvInputCallback}.
+ *
+ * <p>Extends {@code TvInputCallback} in a backwards compatible way.
+ */
+ public static class TvInputCallbackCompat extends TvInputCallback
+ implements TvInputCallbackCompatEvents {
+ private final ArrayMap<String, Integer> inputCompatVersionMap = new ArrayMap<>();
+ private TvViewCompatProcessor mTvViewCompatProcessor;
+
+ @Override
+ public void onEvent(String inputId, String eventType, Bundle eventArgs) {
+ if (mTvViewCompatProcessor != null
+ && !mTvViewCompatProcessor.handleEvent(inputId, eventType, eventArgs)) {
+ super.onEvent(inputId, eventType, eventArgs);
+ }
+ }
+
+ public int getTifCompatVersionForInput(String inputId) {
+ return inputCompatVersionMap.containsKey(inputId)
+ ? inputCompatVersionMap.get(inputId)
+ : 0;
+ }
+
+ @Override
+ public void onDevToast(String inputId, String message) {}
+
+ /**
+ * This is called when the signal strength is notified.
+ *
+ * @param inputId The ID of the TV input bound to this view.
+ * @param value The current signal strength. Should be one of the followings.
+ * <ul>
+ * <li>{@link TvInputConstantCompat#SIGNAL_STRENGTH_NOT_USED}
+ * <li>{@link TvInputConstantCompat#SIGNAL_STRENGTH_ERROR}
+ * <li>{@link TvInputConstantCompat#SIGNAL_STRENGTH_UNKNOWN}
+ * <li>{int [0, 100]}
+ * </ul>
+ */
+ @Override
+ public void onSignalStrength(String inputId, int value) {}
+ }
+}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/compat/api/PrivateCommandSender.java
similarity index 60%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/compat/api/PrivateCommandSender.java
index 3e24a49..11de970 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/compat/api/PrivateCommandSender.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,13 +11,14 @@
* 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.
+ * limitations under the License
*/
+package com.android.tv.common.compat.api;
-package com.android.tv.util;
+import android.os.Bundle;
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+/** Sends a command from the TV App to a {@link android.media.tv.TvInputService.Session} */
+public interface PrivateCommandSender {
+
+ void sendAppPrivateCommand(String action, Bundle data);
}
diff --git a/common/src/com/android/tv/common/compat/api/RecordingClientCallbackCompatEvents.java b/common/src/com/android/tv/common/compat/api/RecordingClientCallbackCompatEvents.java
new file mode 100644
index 0000000..753703c
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/api/RecordingClientCallbackCompatEvents.java
@@ -0,0 +1,27 @@
+/*
+ * 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.api;
+
+/**
+ * {@link android.media.tv.TvRecordingClient} implements this to receive notification from a {@link
+ * android.media.tv.TvInputService.RecordingSession}
+ */
+public interface RecordingClientCallbackCompatEvents {
+ /** Display a message in the application as a toast on dev builds only */
+ void onDevToast(String inputId, String message);
+
+ void onRecordingStarted(String inputId, String recUri);
+}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/compat/api/RecordingSessionCompatCommands.java
similarity index 62%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/compat/api/RecordingSessionCompatCommands.java
index 3e24a49..9deaa41 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/compat/api/RecordingSessionCompatCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,13 +11,12 @@
* 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.
+ * limitations under the License
*/
+package com.android.tv.common.compat.api;
-package com.android.tv.util;
+/** Commands sent from the TV App to {@link android.media.tv.TvInputService.RecordingSession} */
+public interface RecordingSessionCompatCommands {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ void onDevMessage(String message);
}
diff --git a/common/src/com/android/tv/common/compat/api/RecordingSessionCompatEvents.java b/common/src/com/android/tv/common/compat/api/RecordingSessionCompatEvents.java
new file mode 100644
index 0000000..812bba6
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/api/RecordingSessionCompatEvents.java
@@ -0,0 +1,24 @@
+/*
+ * 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.api;
+
+/** Events sent from the {@link android.media.tv.TvInputService.RecordingSession} to the TV App. */
+public interface RecordingSessionCompatEvents {
+
+ void notifyDevToast(String message);
+
+ void notifyRecordingStarted(String value);
+}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/compat/api/SessionCompatCommands.java
similarity index 63%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/compat/api/SessionCompatCommands.java
index 3e24a49..bef4ad2 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/compat/api/SessionCompatCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,13 +11,12 @@
* 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.
+ * limitations under the License
*/
+package com.android.tv.common.compat.api;
-package com.android.tv.util;
+/** Commands sent from the TV App to {@link android.media.tv.TvInputService.Session} */
+public interface SessionCompatCommands {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ void onDevMessage(String message);
}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/compat/api/SessionCompatEvents.java
similarity index 60%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/compat/api/SessionCompatEvents.java
index 3e24a49..a3af8f3 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/compat/api/SessionCompatEvents.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,13 +11,14 @@
* 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.
+ * limitations under the License
*/
+package com.android.tv.common.compat.api;
-package com.android.tv.util;
+/** Events sent from the {@link android.media.tv.TvInputService.Session} to the TV App. */
+public interface SessionCompatEvents {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ void notifyDevToast(String message);
+
+ void notifySignalStrength(int value);
}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/compat/api/SessionEventNotifier.java
similarity index 60%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/compat/api/SessionEventNotifier.java
index 3e24a49..66c5c3a 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/compat/api/SessionEventNotifier.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,13 +11,14 @@
* 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.
+ * limitations under the License
*/
+package com.android.tv.common.compat.api;
-package com.android.tv.util;
+import android.os.Bundle;
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+/** Sends events from a {@link android.media.tv.TvInputService.Session} to the TV App. */
+public interface SessionEventNotifier {
+
+ void notifySessionEvent(String event, Bundle data);
}
diff --git a/common/src/com/android/tv/common/compat/api/TvInputCallbackCompatEvents.java b/common/src/com/android/tv/common/compat/api/TvInputCallbackCompatEvents.java
new file mode 100644
index 0000000..e6b241b
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/api/TvInputCallbackCompatEvents.java
@@ -0,0 +1,26 @@
+/*
+ * 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.api;
+
+/**
+ * {@link android.media.tv.TvView.TvInputCallback} implements this to receive notification from a
+ * {@link android.media.tv.TvInputService.Session}
+ */
+public interface TvInputCallbackCompatEvents {
+ void onDevToast(String inputId, String message);
+
+ void onSignalStrength(String inputId, int value);
+}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/compat/api/TvRecordingClientCompatCommands.java
similarity index 62%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/compat/api/TvRecordingClientCompatCommands.java
index 3e24a49..c185216 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/compat/api/TvRecordingClientCompatCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,13 +11,12 @@
* 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.
+ * limitations under the License
*/
+package com.android.tv.common.compat.api;
-package com.android.tv.util;
+/** Commands sent from the TV App to {@link android.media.tv.TvInputService.RecordingSession} */
+public interface TvRecordingClientCompatCommands {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ void devMessage(String message);
}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/compat/api/TvViewCompatCommands.java
similarity index 63%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/compat/api/TvViewCompatCommands.java
index 3e24a49..5abc6bc 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/compat/api/TvViewCompatCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,13 +11,12 @@
* 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.
+ * limitations under the License
*/
+package com.android.tv.common.compat.api;
-package com.android.tv.util;
+/** Commands sent from the TV App to {@link android.media.tv.TvInputService.Session} */
+public interface TvViewCompatCommands {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ void devMessage(String message);
}
diff --git a/common/src/com/android/tv/common/compat/internal/Constants.java b/common/src/com/android/tv/common/compat/internal/Constants.java
new file mode 100644
index 0000000..993822c
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/Constants.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+/** Static constants use by the TIF compat library */
+final class Constants {
+ static final String ACTION_GET_VERSION = "com.android.tv.common.compat.action.GET_VERSION";
+ static final String EVENT_GET_VERSION = "com.android.tv.common.compat.event.GET_VERSION";
+ static final String ACTION_COMPAT_ON = "com.android.tv.common.compat.action.COMPAT_ON";
+ static final String EVENT_COMPAT_NOTIFY = "com.android.tv.common.compat.event.COMPAT_NOTIFY";
+ static final String EVENT_COMPAT_NOTIFY_ERROR =
+ "com.android.tv.common.compat.event.COMPAT_NOTIFY_ERROR";
+ static final int TIF_COMPAT_VERSION = 1;
+
+ private Constants() {}
+}
diff --git a/common/src/com/android/tv/common/compat/internal/RecordingClientCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/RecordingClientCompatProcessor.java
new file mode 100644
index 0000000..f83228c
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/RecordingClientCompatProcessor.java
@@ -0,0 +1,85 @@
+/*
+ * 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 android.support.annotation.Nullable;
+import android.util.Log;
+import com.android.tv.common.compat.api.PrivateCommandSender;
+import com.android.tv.common.compat.api.RecordingClientCallbackCompatEvents;
+import com.android.tv.common.compat.api.TvViewCompatCommands;
+import com.android.tv.common.compat.internal.Commands.OnDevMessage;
+import com.android.tv.common.compat.internal.Commands.PrivateCommand;
+import com.android.tv.common.compat.internal.RecordingEvents.NotifyDevToast;
+import com.android.tv.common.compat.internal.RecordingEvents.RecordingSessionEvent;
+
+/**
+ * Sends {@link RecordingCommands} to the {@link android.media.tv.TvInputService.RecordingSession}
+ * via {@link PrivateCommandSender} and receives notification events from the session forwarding
+ * them to {@link RecordingClientCallbackCompatEvents}
+ */
+public final class RecordingClientCompatProcessor
+ extends ViewCompatProcessor<PrivateCommand, RecordingSessionEvent>
+ implements TvViewCompatCommands {
+ private static final String TAG = "RecordingClientCompatProcessor";
+
+ @Nullable private final RecordingClientCallbackCompatEvents mCallback;
+
+ public RecordingClientCompatProcessor(
+ PrivateCommandSender commandSender,
+ @Nullable RecordingClientCallbackCompatEvents callback) {
+ super(commandSender, RecordingSessionEvent.parser());
+ mCallback = callback;
+ }
+
+ @Override
+ public void devMessage(String message) {
+ OnDevMessage devMessage = OnDevMessage.newBuilder().setMessage(message).build();
+ PrivateCommand privateCommand =
+ createPrivateCommandCommand().setOnDevMessage(devMessage).build();
+ sendCompatCommand(privateCommand);
+ }
+
+ private PrivateCommand.Builder createPrivateCommandCommand() {
+ return PrivateCommand.newBuilder().setCompatVersion(Constants.TIF_COMPAT_VERSION);
+ }
+
+ @Override
+ protected final void handleSessionEvent(String inputId, RecordingSessionEvent sessionEvent) {
+ switch (sessionEvent.getEventCase()) {
+ case NOTIFY_DEV_MESSAGE:
+ handle(inputId, sessionEvent.getNotifyDevMessage());
+ break;
+ case RECORDING_STARTED:
+ handle(inputId, sessionEvent.getRecordingStarted());
+ break;
+
+ case EVENT_NOT_SET:
+ Log.w(TAG, "Error event not set compat notify ");
+ }
+ }
+
+ private void handle(String inputId, NotifyDevToast devToast) {
+ if (devToast != null && mCallback != null) {
+ mCallback.onDevToast(inputId, devToast.getMessage());
+ }
+ }
+
+ private void handle(String inputId, RecordingEvents.RecordingStarted recStart) {
+ if (recStart != null && mCallback != null) {
+ mCallback.onRecordingStarted(inputId, recStart.getUri());
+ }
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/internal/RecordingSessionCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/RecordingSessionCompatProcessor.java
new file mode 100644
index 0000000..84ec550
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/RecordingSessionCompatProcessor.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.util.Log;
+import com.android.tv.common.compat.api.RecordingSessionCompatCommands;
+import com.android.tv.common.compat.api.RecordingSessionCompatEvents;
+import com.android.tv.common.compat.api.SessionEventNotifier;
+import com.android.tv.common.compat.internal.RecordingCommands.PrivateRecordingCommand;
+import com.android.tv.common.compat.internal.RecordingEvents.NotifyDevToast;
+import com.android.tv.common.compat.internal.RecordingEvents.RecordingSessionEvent;
+import com.android.tv.common.compat.internal.RecordingEvents.RecordingStarted;
+
+/**
+ * Sends {@link RecordingSessionCompatEvents} to the TV App via {@link SessionEventNotifier} and
+ * receives Commands from TV App forwarding them to {@link RecordingSessionCompatProcessor}
+ */
+public final class RecordingSessionCompatProcessor
+ extends SessionCompatProcessor<PrivateRecordingCommand, RecordingSessionEvent>
+ implements RecordingSessionCompatEvents {
+
+ private static final String TAG = "RecordingSessionCompatProc";
+
+ private final RecordingSessionCompatCommands mRecordingSessionOnCompat;
+
+ public RecordingSessionCompatProcessor(
+ SessionEventNotifier sessionEventNotifier,
+ RecordingSessionCompatCommands recordingSessionOnCompat) {
+ super(sessionEventNotifier, PrivateRecordingCommand.parser());
+ mRecordingSessionOnCompat = recordingSessionOnCompat;
+ }
+
+ @Override
+ protected void onCompat(PrivateRecordingCommand privateCommand) {
+ switch (privateCommand.getCommandCase()) {
+ case ON_DEV_MESSAGE:
+ mRecordingSessionOnCompat.onDevMessage(
+ privateCommand.getOnDevMessage().getMessage());
+ break;
+ case COMMAND_NOT_SET:
+ Log.w(TAG, "Command not set ");
+ }
+ }
+
+ @Override
+ public void notifyDevToast(String message) {
+ NotifyDevToast devMessage = NotifyDevToast.newBuilder().setMessage(message).build();
+ RecordingSessionEvent sessionEvent =
+ createSessionEvent().setNotifyDevMessage(devMessage).build();
+ notifyCompat(sessionEvent);
+ }
+
+ @Override
+ public void notifyRecordingStarted(String uri) {
+ RecordingStarted event = RecordingStarted.newBuilder().setUri(uri).build();
+ RecordingSessionEvent sessionEvent =
+ createSessionEvent().setRecordingStarted(event).build();
+ notifyCompat(sessionEvent);
+ }
+
+ private RecordingSessionEvent.Builder createSessionEvent() {
+
+ return RecordingSessionEvent.newBuilder().setCompatVersion(Constants.TIF_COMPAT_VERSION);
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/internal/SessionCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/SessionCompatProcessor.java
new file mode 100644
index 0000000..7f27a24
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/SessionCompatProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * 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 android.os.Bundle;
+import android.util.Log;
+import com.android.tv.common.compat.api.SessionEventNotifier;
+import com.google.protobuf.GeneratedMessageLite;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Parser;
+
+/**
+ * Sends {@code events} to the TV App via {@link SessionEventNotifier} and receives {@code commands}
+ * from TV App.
+ */
+abstract class SessionCompatProcessor<
+ C extends GeneratedMessageLite<C, ?>, E extends GeneratedMessageLite<E, ?>> {
+ private static final String TAG = "SessionCompatProcessor";
+ private final SessionEventNotifier mSessionEventNotifier;
+ private final Parser<C> mCommandParser;
+
+ SessionCompatProcessor(SessionEventNotifier sessionEventNotifier, Parser<C> commandParser) {
+ mSessionEventNotifier = sessionEventNotifier;
+ mCommandParser = commandParser;
+ }
+
+ public final boolean handleAppPrivateCommand(String action, Bundle data) {
+ switch (action) {
+ case Constants.ACTION_GET_VERSION:
+ Bundle response = new Bundle();
+ response.putInt(Constants.EVENT_GET_VERSION, Constants.TIF_COMPAT_VERSION);
+ mSessionEventNotifier.notifySessionEvent(Constants.EVENT_GET_VERSION, response);
+ return true;
+ case Constants.ACTION_COMPAT_ON:
+ byte[] bytes = data.getByteArray(Constants.ACTION_COMPAT_ON);
+ try {
+ C privateCommand = mCommandParser.parseFrom(bytes);
+ onCompat(privateCommand);
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG, "Error parsing compat data", e);
+ }
+
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ abstract void onCompat(C privateCommand);
+
+ final void notifyCompat(E event) {
+ Bundle response = new Bundle();
+ try {
+ byte[] bytes = event.toByteArray();
+ response.putByteArray(Constants.EVENT_COMPAT_NOTIFY, bytes);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to send " + event, e);
+ response.putString(Constants.EVENT_COMPAT_NOTIFY_ERROR, e.getMessage());
+ }
+ mSessionEventNotifier.notifySessionEvent(Constants.EVENT_COMPAT_NOTIFY, response);
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/internal/TifSessionCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/TifSessionCompatProcessor.java
new file mode 100644
index 0000000..dd7a3b3
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/TifSessionCompatProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * 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 android.util.Log;
+import com.android.tv.common.compat.api.SessionCompatCommands;
+import com.android.tv.common.compat.api.SessionCompatEvents;
+import com.android.tv.common.compat.api.SessionEventNotifier;
+import com.android.tv.common.compat.internal.Commands.PrivateCommand;
+import com.android.tv.common.compat.internal.Events.NotifyDevToast;
+import com.android.tv.common.compat.internal.Events.NotifySignalStrength;
+import com.android.tv.common.compat.internal.Events.SessionEvent;
+
+/**
+ * Sends {@link SessionCompatEvents} to the TV App via {@link SessionEventNotifier} and receives
+ * Commands from TV App forwarding them to {@link SessionCompatCommands}
+ */
+public final class TifSessionCompatProcessor
+ extends SessionCompatProcessor<PrivateCommand, SessionEvent>
+ implements SessionCompatEvents {
+
+ private static final String TAG = "TifSessionCompatProcessor";
+
+ private final SessionCompatCommands mSessionOnCompat;
+
+ public TifSessionCompatProcessor(
+ SessionEventNotifier sessionEventNotifier, SessionCompatCommands sessionOnCompat) {
+ super(sessionEventNotifier, PrivateCommand.parser());
+ mSessionOnCompat = sessionOnCompat;
+ }
+
+ @Override
+ protected void onCompat(Commands.PrivateCommand privateCommand) {
+ switch (privateCommand.getCommandCase()) {
+ case ON_DEV_MESSAGE:
+ mSessionOnCompat.onDevMessage(privateCommand.getOnDevMessage().getMessage());
+ break;
+ case COMMAND_NOT_SET:
+ Log.w(TAG, "Command not set ");
+ }
+ }
+
+ @Override
+ public void notifyDevToast(String message) {
+ NotifyDevToast devMessage = NotifyDevToast.newBuilder().setMessage(message).build();
+ SessionEvent sessionEvent = createSessionEvent().setNotifyDevMessage(devMessage).build();
+ notifyCompat(sessionEvent);
+ }
+
+ @Override
+ public void notifySignalStrength(int value) {
+ NotifySignalStrength signalStrength =
+ NotifySignalStrength.newBuilder().setSignalStrength(value).build();
+ SessionEvent sessionEvent =
+ createSessionEvent().setNotifySignalStrength(signalStrength).build();
+ notifyCompat(sessionEvent);
+ }
+
+ private SessionEvent.Builder createSessionEvent() {
+ return SessionEvent.newBuilder().setCompatVersion(Constants.TIF_COMPAT_VERSION);
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/internal/TvViewCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/TvViewCompatProcessor.java
new file mode 100644
index 0000000..382f8d8
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/TvViewCompatProcessor.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.support.annotation.NonNull;
+import android.util.Log;
+import com.android.tv.common.compat.api.PrivateCommandSender;
+import com.android.tv.common.compat.api.TvInputCallbackCompatEvents;
+import com.android.tv.common.compat.api.TvViewCompatCommands;
+import com.android.tv.common.compat.internal.Commands.OnDevMessage;
+import com.android.tv.common.compat.internal.Commands.PrivateCommand;
+import com.android.tv.common.compat.internal.Events.NotifyDevToast;
+import com.android.tv.common.compat.internal.Events.NotifySignalStrength;
+import com.android.tv.common.compat.internal.Events.SessionEvent;
+
+/**
+ * Sends {@link TvViewCompatCommands} to the {@link android.media.tv.TvInputService.Session} via
+ * {@link PrivateCommandSender} and receives notification events from the session forwarding them to
+ * {@link TvInputCallbackCompatEvents}
+ */
+public final class TvViewCompatProcessor extends ViewCompatProcessor<PrivateCommand, SessionEvent>
+ implements TvViewCompatCommands {
+ private static final String TAG = "TvViewCompatProcessor";
+
+ private TvInputCallbackCompatEvents mCallback;
+
+ public TvViewCompatProcessor(PrivateCommandSender commandSender) {
+ super(commandSender, SessionEvent.parser());
+ }
+
+ @Override
+ public void devMessage(String message) {
+ OnDevMessage devMessage = Commands.OnDevMessage.newBuilder().setMessage(message).build();
+ Commands.PrivateCommand privateCommand =
+ createPrivateCommandCommand().setOnDevMessage(devMessage).build();
+ sendCompatCommand(privateCommand);
+ }
+
+ @NonNull
+ private PrivateCommand.Builder createPrivateCommandCommand() {
+ return PrivateCommand.newBuilder().setCompatVersion(Constants.TIF_COMPAT_VERSION);
+ }
+
+ public void onDevToast(String inputId, String message) {}
+
+ public void onSignalStrength(String inputId, int value) {}
+
+ @Override
+ protected final void handleSessionEvent(String inputId, Events.SessionEvent sessionEvent) {
+ switch (sessionEvent.getEventCase()) {
+ case NOTIFY_DEV_MESSAGE:
+ handle(inputId, sessionEvent.getNotifyDevMessage());
+ break;
+ case NOTIFY_SIGNAL_STRENGTH:
+ handle(inputId, sessionEvent.getNotifySignalStrength());
+ break;
+ case EVENT_NOT_SET:
+ Log.w(TAG, "Error event not set compat notify ");
+ }
+ }
+
+ private void handle(String inputId, NotifyDevToast devToast) {
+ if (devToast != null && mCallback != null) {
+ mCallback.onDevToast(inputId, devToast.getMessage());
+ }
+ }
+
+ private void handle(String inputId, NotifySignalStrength signalStrength) {
+ if (signalStrength != null && mCallback != null) {
+ mCallback.onSignalStrength(inputId, signalStrength.getSignalStrength());
+ }
+ }
+
+ public void setCallback(TvInputCallbackCompatEvents callback) {
+ this.mCallback = callback;
+ }
+}
diff --git a/common/src/com/android/tv/common/compat/internal/ViewCompatProcessor.java b/common/src/com/android/tv/common/compat/internal/ViewCompatProcessor.java
new file mode 100644
index 0000000..dc6bbfa
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/ViewCompatProcessor.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.os.Bundle;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.tv.common.compat.api.PrivateCommandSender;
+import com.google.protobuf.GeneratedMessageLite;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Parser;
+
+/**
+ * Sends {@code commands} to the {@code session} via {@link PrivateCommandSender} and receives
+ * notification events from the session forwarding them to {@link
+ * com.android.tv.common.compat.api.TvInputCallbackCompatEvents}
+ */
+abstract class ViewCompatProcessor<
+ C extends GeneratedMessageLite<C, ?>, E extends GeneratedMessageLite<E, ?>> {
+ private static final String TAG = "ViewCompatProcessor";
+ private final ArrayMap<String, Integer> inputCompatVersionMap = new ArrayMap<>();
+
+ private final Parser<E> mEventParser;
+ private final PrivateCommandSender mCommandSender;
+
+ ViewCompatProcessor(PrivateCommandSender commandSender, Parser<E> eventParser) {
+ mCommandSender = commandSender;
+ mEventParser = eventParser;
+ }
+
+ private final E sessionEventFromBundle(Bundle eventArgs) throws InvalidProtocolBufferException {
+
+ byte[] protoBytes = eventArgs.getByteArray(Constants.EVENT_COMPAT_NOTIFY);
+ return protoBytes == null || protoBytes.length == 0
+ ? null
+ : mEventParser.parseFrom(protoBytes);
+ }
+
+ final void sendCompatCommand(C privateCommand) {
+ try {
+ Bundle data = new Bundle();
+ data.putByteArray(Constants.ACTION_COMPAT_ON, privateCommand.toByteArray());
+ mCommandSender.sendAppPrivateCommand(Constants.ACTION_COMPAT_ON, data);
+ } catch (Exception e) {
+ Log.w(TAG, "Error sending compat action " + privateCommand, e);
+ }
+ }
+
+ public boolean handleEvent(String inputId, String eventType, Bundle eventArgs) {
+ switch (eventType) {
+ case Constants.EVENT_GET_VERSION:
+ int version = eventArgs.getInt(Constants.EVENT_GET_VERSION, 0);
+ inputCompatVersionMap.put(inputId, version);
+ return true;
+ case Constants.EVENT_COMPAT_NOTIFY:
+ try {
+ E sessionEvent = sessionEventFromBundle(eventArgs);
+ if (sessionEvent != null) {
+ handleSessionEvent(inputId, sessionEvent);
+ } else {
+ String errorMessage =
+ eventArgs.getString(Constants.EVENT_COMPAT_NOTIFY_ERROR);
+ Log.w(TAG, "Error sent in compat notify " + errorMessage);
+ }
+
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG, "Error parsing in compat notify for " + inputId);
+ }
+
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ protected abstract void handleSessionEvent(String inputId, E sessionEvent);
+}
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
new file mode 100644
index 0000000..ce59bfa
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/recording_commands.proto
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+// A set of Private Commands to send to a TVInputService.Session, in particular
+// 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 = "RecordingCommands";
+option java_package = "com.android.tv.common.compat.internal";
+
+// Wraps messages for sending to a session a private command.
+message PrivateRecordingCommand {
+ uint32 compat_version = 1;
+
+ oneof command {
+ OnDevMessage on_dev_message = 2;
+ }
+}
+
+// Display a debug message dev builds only.
+message OnDevMessage {
+ string message = 1;
+}
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
new file mode 100644
index 0000000..68db5dd
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/recording_events.proto
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+// A set of Session events to send from a TVInputService.Session, in particular
+// 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";
+option java_package = "com.android.tv.common.compat.internal";
+
+// Wraps messages for sending from a session as an Event.
+// RecordingSessionCompat will have a notify{EventMessageName} for each event
+// TvRecordingClientCompat will have a on{EventMessageName} for each event
+
+message RecordingSessionEvent {
+ uint32 compat_version = 1;
+
+ oneof event {
+ NotifyDevToast notify_dev_message = 2;
+ RecordingStarted recording_started = 3;
+ }
+}
+
+// Display a message as a toast on dev builds only
+message NotifyDevToast {
+ string message = 1;
+}
+
+// Recording started.
+message RecordingStarted {
+ // 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
new file mode 100644
index 0000000..d586770
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/tif_commands.proto
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+// A set of Private Commands to send to a TVInputService.Session, in particular
+// 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 = "Commands";
+option java_package = "com.android.tv.common.compat.internal";
+
+// Wraps messages for sending to a session a private command.
+message PrivateCommand {
+ uint32 compat_version = 1;
+
+ oneof command {
+ OnDevMessage on_dev_message = 2;
+ }
+}
+
+// Sends a debug message to the session for display on dev builds only
+message OnDevMessage {
+ string message = 1;
+}
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
new file mode 100644
index 0000000..6e71ae1
--- /dev/null
+++ b/common/src/com/android/tv/common/compat/internal/tif_events.proto
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+// A set of Session events to send from a TVInputService.Session, in particular
+// 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";
+option java_package = "com.android.tv.common.compat.internal";
+
+// Wraps messages for sending from a session as an Event.
+message SessionEvent {
+ uint32 compat_version = 1;
+
+ oneof event {
+ NotifyDevToast notify_dev_message = 2;
+ NotifySignalStrength notify_signal_strength = 3;
+ }
+}
+
+// Send a message to the application to be displayed as a toast on dev builds
+// only
+message NotifyDevToast {
+ string message = 1;
+}
+
+// Notifies the TV Application the current signal strength.
+message NotifySignalStrength {
+ // The signal strength as a percent (0 to 100),
+ // with -1 meaning unknown, -2 meaning not used.
+ int32 signal_strength = 1;
+}
diff --git a/common/src/com/android/tv/common/config/DefaultConfigManager.java b/common/src/com/android/tv/common/config/DefaultConfigManager.java
deleted file mode 100644
index ae24085..0000000
--- a/common/src/com/android/tv/common/config/DefaultConfigManager.java
+++ /dev/null
@@ -1,60 +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.config;
-
-import android.content.Context;
-import com.android.tv.common.config.api.RemoteConfig;
-
-/** Stub Remote Config. */
-public class DefaultConfigManager {
- public static final long DEFAULT_LONG_VALUE = 0;
-
- public static DefaultConfigManager createInstance(Context context) {
- return new DefaultConfigManager();
- }
-
- private StubRemoteConfig mRemoteConfig = new StubRemoteConfig();
-
- public RemoteConfig getRemoteConfig() {
- return mRemoteConfig;
- }
-
- private static class StubRemoteConfig implements RemoteConfig {
- @Override
- public void fetch(OnRemoteConfigUpdatedListener listener) {}
-
- @Override
- public String getString(String key) {
- return null;
- }
-
- @Override
- public boolean getBoolean(String key) {
- return false;
- }
-
- @Override
- public long getLong(String key) {
- return DEFAULT_LONG_VALUE;
- }
-
- @Override
- public long getLong(String key, long defaultValue) {
- return defaultValue;
- }
- }
-}
diff --git a/common/src/com/android/tv/common/config/RemoteConfigFeature.java b/common/src/com/android/tv/common/config/RemoteConfigFeature.java
deleted file mode 100644
index 2ea381f..0000000
--- a/common/src/com/android/tv/common/config/RemoteConfigFeature.java
+++ /dev/null
@@ -1,42 +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.config;
-
-import android.content.Context;
-import com.android.tv.common.BaseApplication;
-import com.android.tv.common.feature.Feature;
-
-/**
- * A {@link Feature} controlled by a {@link com.android.tv.common.config.api.RemoteConfig} boolean.
- */
-public class RemoteConfigFeature implements Feature {
- private final String mKey;
-
- /** Creates a {@link RemoteConfigFeature for the {@code key}. */
- public static RemoteConfigFeature fromKey(String key) {
- return new RemoteConfigFeature(key);
- }
-
- private RemoteConfigFeature(String key) {
- mKey = key;
- }
-
- @Override
- public boolean isEnabled(Context context) {
- return BaseApplication.getSingletons(context).getRemoteConfig().getBoolean(mKey);
- }
-}
diff --git a/common/src/com/android/tv/common/config/api/RemoteConfig.java b/common/src/com/android/tv/common/config/api/RemoteConfig.java
deleted file mode 100644
index 74597f9..0000000
--- a/common/src/com/android/tv/common/config/api/RemoteConfig.java
+++ /dev/null
@@ -1,54 +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.config.api;
-
-/**
- * Manages Live TV Configuration, allowing remote updates.
- *
- * <p>This is a thin wrapper around <a
- * href="https://firebase.google.com/docs/remote-config/"></a>Firebase Remote Config</a>
- */
-public interface RemoteConfig {
-
- /** Used to inject a remote config */
- interface HasRemoteConfig {
- RemoteConfig getRemoteConfig();
- }
-
- /** Notified on successful completion of a {@link #fetch)} */
- interface OnRemoteConfigUpdatedListener {
- void onRemoteConfigUpdated();
- }
-
- /** Starts a fetch and notifies {@code listener} on successful completion. */
- void fetch(OnRemoteConfigUpdatedListener listener);
-
- /** Gets value as a string corresponding to the specified key. */
- String getString(String key);
-
- /** Gets value as a boolean corresponding to the specified key. */
- boolean getBoolean(String key);
-
- /** Gets value as a long corresponding to the specified key. */
- long getLong(String key);
-
- /**
- * Gets value as a long corresponding to the specified key. Returns the defaultValue if no value
- * is found.
- */
- long getLong(String key, long defaultValue);
-}
diff --git a/common/src/com/android/tv/common/config/api/RemoteConfigValue.java b/common/src/com/android/tv/common/config/api/RemoteConfigValue.java
deleted file mode 100644
index 6da89fb..0000000
--- a/common/src/com/android/tv/common/config/api/RemoteConfigValue.java
+++ /dev/null
@@ -1,48 +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.config.api;
-
-/** Wrapper for a RemoteConfig key and default value. */
-public abstract class RemoteConfigValue<T> {
- private final T defaultValue;
- private final String key;
-
- private RemoteConfigValue(String key, T defaultValue) {
- this.defaultValue = defaultValue;
- this.key = key;
- }
-
- /** Create with the given key and default value */
- public static RemoteConfigValue<Long> create(String key, long defaultValue) {
- return new RemoteConfigValue<Long>(key, defaultValue) {
- @Override
- public Long get(RemoteConfig remoteConfig) {
- return remoteConfig.getLong(key, defaultValue);
- }
- };
- }
-
- public abstract T get(RemoteConfig remoteConfig);
-
- public final T getDefaultValue() {
- return defaultValue;
- }
-
- @Override
- public final String toString() {
- return "RemoteConfigValue(key=" + key + ", defalutValue=" + defaultValue + "]";
- }
-}
diff --git a/common/src/com/android/tv/common/dagger/ApplicationModule.java b/common/src/com/android/tv/common/dagger/ApplicationModule.java
new file mode 100644
index 0000000..4655f77
--- /dev/null
+++ b/common/src/com/android/tv/common/dagger/ApplicationModule.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import android.app.Application;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Looper;
+import com.android.tv.common.dagger.annotations.ApplicationContext;
+import com.android.tv.common.dagger.annotations.MainLooper;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides application-scope qualifiers for the {@link Application}, the application context, and
+ * the application's main looper.
+ */
+@Module
+public final class ApplicationModule {
+ private final Application mApplication;
+
+ public ApplicationModule(Application application) {
+ mApplication = application;
+ }
+
+ @Provides
+ Application provideApplication() {
+ return mApplication;
+ }
+
+ @Provides
+ @ApplicationContext
+ Context provideContext() {
+ return mApplication.getApplicationContext();
+ }
+
+ @Provides
+ @MainLooper
+ static Looper provideMainLooper() {
+ return Looper.getMainLooper();
+ }
+
+ @Provides
+ ContentResolver provideContentResolver() {
+ return mApplication.getContentResolver();
+ }
+}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/dagger/annotations/ApplicationContext.java
similarity index 67%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/dagger/annotations/ApplicationContext.java
index 3e24a49..8631815 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/dagger/annotations/ApplicationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv.common.dagger.annotations;
-package com.android.tv.util;
+import javax.inject.Qualifier;
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
-}
+/** Annotation for requesting the application's context. */
+@Qualifier
+public @interface ApplicationContext {}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/dagger/annotations/MainLooper.java
similarity index 67%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/dagger/annotations/MainLooper.java
index 3e24a49..a8b4100 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/dagger/annotations/MainLooper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv.common.dagger.annotations;
-package com.android.tv.util;
+import javax.inject.Qualifier;
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
-}
+/** Annotation for requesting a Looper that is on the UI thread. */
+@Qualifier
+public @interface MainLooper {}
diff --git a/common/src/com/android/tv/common/data/RecordedProgramState.java b/common/src/com/android/tv/common/data/RecordedProgramState.java
new file mode 100644
index 0000000..3bfad88
--- /dev/null
+++ b/common/src/com/android/tv/common/data/RecordedProgramState.java
@@ -0,0 +1,14 @@
+package com.android.tv.common.data;
+
+/** The recording state. */
+// TODO(b/25023911): Use @SimpleEnum when it is supported by AutoValue
+public enum RecordedProgramState {
+ // TODO(b/71717809): Document each state.
+ NOT_SET,
+ STARTED,
+ FINISHED,
+ PARTIAL,
+ FAILED,
+ DELETE,
+ DELETED,
+}
diff --git a/common/src/com/android/tv/common/experiments/ExperimentFlag.java b/common/src/com/android/tv/common/experiments/ExperimentFlag.java
index c9bacac..b8370ad 100644
--- a/common/src/com/android/tv/common/experiments/ExperimentFlag.java
+++ b/common/src/com/android/tv/common/experiments/ExperimentFlag.java
@@ -19,41 +19,63 @@
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;
@@ -64,4 +86,11 @@
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/Experiments.java b/common/src/com/android/tv/common/experiments/Experiments.java
index 96b15e5..9bfdb54 100644
--- a/common/src/com/android/tv/common/experiments/Experiments.java
+++ b/common/src/com/android/tv/common/experiments/Experiments.java
@@ -19,6 +19,7 @@
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.
@@ -26,17 +27,15 @@
* <p>This file is maintained by hand.
*/
public final class Experiments {
- public static final ExperimentFlag<Boolean> CLOUD_EPG =
- ExperimentFlag.createFlag(
- true);
-
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);
/**
@@ -46,6 +45,7 @@
*/
public static final ExperimentFlag<Boolean> ENABLE_DEVELOPER_FEATURES =
ExperimentFlag.createFlag(
+// AOSP_Comment_Out LiveChannels::enableDeveloperFeatures,
BuildConfig.ENG);
/**
@@ -57,6 +57,7 @@
*/
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/EngOnlyFeature.java b/common/src/com/android/tv/common/feature/BuildTypeFeature.java
similarity index 66%
rename from common/src/com/android/tv/common/feature/EngOnlyFeature.java
rename to common/src/com/android/tv/common/feature/BuildTypeFeature.java
index 5feb548..9e1704e 100644
--- a/common/src/com/android/tv/common/feature/EngOnlyFeature.java
+++ b/common/src/com/android/tv/common/feature/BuildTypeFeature.java
@@ -20,18 +20,23 @@
import com.android.tv.common.BuildConfig;
/** A feature that is only available on {@link BuildConfig#ENG} builds. */
-public final class EngOnlyFeature implements Feature {
- public static final Feature ENG_ONLY_FEATURE = new EngOnlyFeature();
+public final class BuildTypeFeature implements Feature {
+ public static final Feature ENG_ONLY_FEATURE = new BuildTypeFeature(BuildConfig.ENG);
+ public static final Feature ASOP_FEATURE = new BuildTypeFeature(BuildConfig.AOSP);
- private EngOnlyFeature() {}
+ private final boolean mIsBuildType;
+
+ private BuildTypeFeature(boolean isBuildType) {
+ mIsBuildType = isBuildType;
+ }
@Override
public boolean isEnabled(Context context) {
- return BuildConfig.ENG;
+ return mIsBuildType;
}
@Override
public String toString() {
- return "EngOnlyFeature(" + BuildConfig.ENG + ")";
+ return getClass().getSimpleName() + "(" + mIsBuildType + ")";
}
}
diff --git a/common/src/com/android/tv/common/feature/CommonFeatures.java b/common/src/com/android/tv/common/feature/CommonFeatures.java
index 1fceabb..04052a7 100644
--- a/common/src/com/android/tv/common/feature/CommonFeatures.java
+++ b/common/src/com/android/tv/common/feature/CommonFeatures.java
@@ -16,18 +16,16 @@
package com.android.tv.common.feature;
-import static com.android.tv.common.feature.FeatureUtils.AND;
+import static com.android.tv.common.feature.BuildTypeFeature.ENG_ONLY_FEATURE;
+import static com.android.tv.common.feature.FeatureUtils.and;
+import static com.android.tv.common.feature.FeatureUtils.or;
import static com.android.tv.common.feature.TestableFeature.createTestableFeature;
import android.content.Context;
-import android.os.Build;
-import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.common.config.api.RemoteConfig.HasRemoteConfig;
-import com.android.tv.common.experiments.Experiments;
-
-import com.android.tv.common.util.CommonUtils;
+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.
@@ -46,7 +44,7 @@
* <p>DVR API is introduced in N, it only works when app runs as a system app.
*/
public static final TestableFeature DVR =
- createTestableFeature(AND(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE));
+ createTestableFeature(and(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE));
/**
* ENABLE_RECORDING_REGARDLESS_OF_STORAGE_STATUS
@@ -56,44 +54,32 @@
public static final Feature FORCE_RECORDING_UNTIL_NO_SPACE =
PropertyFeature.create("force_recording_until_no_space", false);
- public static final Feature TUNER =
- new Feature() {
- @Override
- public boolean isEnabled(Context context) {
-
- if (CommonUtils.isDeveloper()) {
- // we enable tuner for developers to test tuner in any platform.
- return true;
- }
-
- // This is special handling just for USB Tuner.
- // It does not require any N API's but relies on a improvements in N for AC3
- // support
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
- }
- };
-
/** Show postal code fragment before channel scan. */
public static final Feature ENABLE_CLOUD_EPG_REGION =
- new Feature() {
- private final String[] supportedRegions = {
- };
+ or(
+ FlagFeature.from(HasCloudEpgFlags::fromContext, CloudEpgFlags::supportedRegion),
+ new Feature() {
+ private final String[] supportedRegions = {
+// AOSP_Comment_Out "US", "GB"
+ };
-
- @Override
- public boolean isEnabled(Context context) {
- if (!Experiments.CLOUD_EPG.get()) {
- if (DEBUG) Log.d(TAG, "Experiments.CLOUD_EPG is false");
- return false;
- }
- String country = LocationUtils.getCurrentCountry(context);
- for (int i = 0; i < supportedRegions.length; i++) {
- if (supportedRegions[i].equalsIgnoreCase(country)) {
- return true;
+ @Override
+ public boolean isEnabled(Context context) {
+ String country = LocationUtils.getCurrentCountry(context);
+ for (int i = 0; i < supportedRegions.length; i++) {
+ if (supportedRegions[i].equalsIgnoreCase(country)) {
+ return true;
+ }
+ }
+ if (DEBUG) Log.d(TAG, "EPG flag false after country check");
+ return false;
}
- }
- if (DEBUG) Log.d(TAG, "EPG flag false after country check");
- return false;
- }
- };
+ });
+
+ // TODO(b/74197177): remove when UI and API finalized.
+ /** Show channel signal strength. */
+ public static final Feature TUNER_SIGNAL_STRENGTH = ENG_ONLY_FEATURE;
+
+ /** Use AudioOnlyTvService for audio-only inputs. */
+ public static final Feature ENABLE_TV_SERVICE = ENG_ONLY_FEATURE;
}
diff --git a/common/src/com/android/tv/common/feature/FeatureUtils.java b/common/src/com/android/tv/common/feature/FeatureUtils.java
index 8650d15..aaed6c8 100644
--- a/common/src/com/android/tv/common/feature/FeatureUtils.java
+++ b/common/src/com/android/tv/common/feature/FeatureUtils.java
@@ -17,6 +17,7 @@
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;
@@ -28,7 +29,7 @@
*
* @param features the features to or
*/
- public static Feature OR(final Feature... features) {
+ public static Feature or(final Feature... features) {
return new Feature() {
@Override
public boolean isEnabled(Context context) {
@@ -52,7 +53,7 @@
*
* @param features the features to and
*/
- public static Feature AND(final Feature... features) {
+ public static Feature and(final Feature... features) {
return new Feature() {
@Override
public boolean isEnabled(Context context) {
@@ -70,6 +71,42 @@
}
};
}
+ /**
+ * 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}.
+ *
+ * @param feature the feature to invert
+ */
+ public static Feature not(final Feature feature) {
+ return new Feature() {
+ @Override
+ public boolean isEnabled(Context context) {
+ return !feature.isEnabled(context);
+ }
+
+ @Override
+ public String toString() {
+ return "not(" + feature + ")";
+ }
+ };
+ }
/** A feature that is always enabled. */
public static final Feature ON =
diff --git a/common/src/com/android/tv/common/feature/FlagFeature.java b/common/src/com/android/tv/common/feature/FlagFeature.java
new file mode 100644
index 0000000..8da470e
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/FlagFeature.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.feature;
+
+import android.content.Context;
+import com.google.common.base.Function;
+
+/** Feature from a Flag */
+public class FlagFeature<T> implements Feature {
+
+ private final Function<Context, T> mToFlag;
+ private final Function<T, Boolean> mToBoolean;
+
+ public static <T> FlagFeature<T> from(
+ Function<Context, T> toFlag, Function<T, Boolean> toBoolean) {
+ return new FlagFeature<T>(toFlag, toBoolean);
+ }
+
+ private FlagFeature(Function<Context, T> toFlag, Function<T, Boolean> toBoolean) {
+ mToFlag = toFlag;
+ mToBoolean = toBoolean;
+ }
+
+ @Override
+ public boolean isEnabled(Context context) {
+ return mToBoolean.apply(mToFlag.apply(context));
+ }
+
+ @Override
+ public String toString() {
+ return mToBoolean.toString();
+ }
+}
diff --git a/common/src/com/android/tv/common/feature/GServiceFeature.java b/common/src/com/android/tv/common/feature/GServiceFeature.java
deleted file mode 100644
index 1d7d115..0000000
--- a/common/src/com/android/tv/common/feature/GServiceFeature.java
+++ /dev/null
@@ -1,43 +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.common.feature;
-
-import android.content.Context;
-
-/** A feature controlled by a GServices flag. */
-public class GServiceFeature implements Feature {
- private static final String LIVECHANNELS_PREFIX = "livechannels:";
- private final String mKey;
- private final boolean mDefaultValue;
-
- public GServiceFeature(String key, boolean defaultValue) {
- mKey = LIVECHANNELS_PREFIX + key;
- mDefaultValue = defaultValue;
- }
-
- @Override
- public boolean isEnabled(Context context) {
-
- // GServices is not available outside of Google.
- return mDefaultValue;
- }
-
- @Override
- public String toString() {
- return "GService[hash=" + mKey.hashCode() + "]";
- }
-}
diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java
index 155b391..4b0a925 100644
--- a/common/src/com/android/tv/common/feature/Sdk.java
+++ b/common/src/com/android/tv/common/feature/Sdk.java
@@ -17,25 +17,33 @@
package com.android.tv.common.feature;
import android.content.Context;
-import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
/** Holder for SDK version features */
public final class Sdk {
- public static final Feature AT_LEAST_N =
- new Feature() {
- @Override
- public boolean isEnabled(Context context) {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
- }
- };
- public static final Feature AT_LEAST_O =
- new Feature() {
- @Override
- public boolean isEnabled(Context context) {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
- }
- };
+ public static final Feature AT_LEAST_M = new AtLeast(VERSION_CODES.M);
+
+ public static final Feature AT_LEAST_N = new AtLeast(VERSION_CODES.N);
+
+ 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;
+
+ private AtLeast(int versionCode) {
+ this.versionCode = versionCode;
+ }
+
+ @Override
+ public boolean isEnabled(Context unused) {
+ return VERSION.SDK_INT >= versionCode;
+ }
+ }
private Sdk() {}
}
diff --git a/common/src/com/android/tv/common/flags/BackendKnobsFlags.java b/common/src/com/android/tv/common/flags/BackendKnobsFlags.java
new file mode 100644
index 0000000..69bac7a
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/BackendKnobsFlags.java
@@ -0,0 +1,46 @@
+/*
+ * 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 tuning non ui behavior */
+public interface BackendKnobsFlags {
+
+ /**
+ * 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 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();
+
+ /** How many hours of programs are loaded in the program guide */
+ long programGuideMaxHours();
+}
diff --git a/common/src/com/android/tv/common/flags/CloudEpgFlags.java b/common/src/com/android/tv/common/flags/CloudEpgFlags.java
new file mode 100755
index 0000000..ab4c6a1
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/CloudEpgFlags.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;
+
+/** Flags for Cloud EPG */
+public interface CloudEpgFlags {
+
+ /**
+ * 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();
+
+ /** Is the device in a region supported by Cloud Epg */
+ boolean supportedRegion();
+
+ /** List of input ids that Live TV 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
new file mode 100755
index 0000000..1afff79
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/ConcurrentDvrPlaybackFlags.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;
+
+/** Flags allowing concurrent DVR playback */
+public interface ConcurrentDvrPlaybackFlags {
+
+ /**
+ * 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 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/TunerFlags.java b/common/src/com/android/tv/common/flags/TunerFlags.java
new file mode 100755
index 0000000..5f899b9
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/TunerFlags.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;
+
+/** Flags for tuner */
+public interface TunerFlags {
+
+ /**
+ * 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();
+
+ /** 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
new file mode 100755
index 0000000..4c88d08
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/UiFlags.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.flags;
+
+/** Flags for Live TV UI */
+public interface UiFlags {
+
+ /**
+ * 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();
+
+ /**
+ * Number of days to be shown by Recording History.
+ *
+ * <p>Set to 0 for all recordings.
+ */
+ long maxHistoryDays();
+
+ /** 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/HasCloudEpgFlags.java b/common/src/com/android/tv/common/flags/has/HasCloudEpgFlags.java
new file mode 100644
index 0000000..c33c552
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/has/HasCloudEpgFlags.java
@@ -0,0 +1,29 @@
+/*
+ * 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.CloudEpgFlags;
+
+/** Has {@link CloudEpgFlags} */
+public interface HasCloudEpgFlags {
+
+ static CloudEpgFlags fromContext(Context context) {
+ return ((HasCloudEpgFlags) HasUtils.getApplicationContext(context)).getCloudEpgFlags();
+ }
+
+ CloudEpgFlags getCloudEpgFlags();
+}
diff --git a/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java b/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java
new file mode 100644
index 0000000..b471087
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/has/HasConcurrentDvrPlaybackFlags.java
@@ -0,0 +1,30 @@
+/*
+ * 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/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/flags/has/HasUiFlags.java
similarity index 63%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/flags/has/HasUiFlags.java
index 3e24a49..72cc84f 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/flags/has/HasUiFlags.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,13 +11,14 @@
* 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.
+ * limitations under the License
*/
+package com.android.tv.common.flags.has;
-package com.android.tv.util;
+import com.android.tv.common.flags.UiFlags;
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+/** Has {@link UiFlags} */
+public interface HasUiFlags {
+
+ UiFlags getUiFlags();
}
diff --git a/common/src/com/android/tv/common/flags/has/HasUtils.java b/common/src/com/android/tv/common/flags/has/HasUtils.java
new file mode 100644
index 0000000..1c6126d
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/has/HasUtils.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/** Static utilities for Has interfaces. */
+public final class HasUtils {
+
+ /** Returns the application context. */
+ public static Context getApplicationContext(Context context) {
+ Context appContext = context.getApplicationContext();
+ return appContext != null ? appContext : context;
+ }
+
+ private HasUtils() {}
+}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java
new file mode 100644
index 0000000..a189e47
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultBackendKnobsFlags.java
@@ -0,0 +1,56 @@
+/*
+ * 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 DefaultBackendKnobsFlags
+ implements com.android.tv.common.flags.BackendKnobsFlags {
+
+ @Override
+ public boolean compiled() {
+ return true;
+ }
+
+ @Override
+ public boolean enablePartialProgramFetch() {
+ return false;
+ }
+
+ @Override
+ public long epgFetcherIntervalHour() {
+ return 25;
+ }
+
+ @Override
+ public boolean fetchProgramsAsNeeded() {
+ return false;
+ }
+
+ @Override
+ public long programGuideInitialFetchHours() {
+ return 8;
+ }
+
+ @Override
+ public long programGuideMaxHours() {
+ return 336;
+ }
+
+ @Override
+ public long epgTargetChannelCount() {
+ return 100;
+ }
+}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultCloudEpgFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultCloudEpgFlags.java
new file mode 100644
index 0000000..34c4fc4
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultCloudEpgFlags.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.flags.impl;
+
+import com.android.tv.common.flags.CloudEpgFlags;
+
+/** Default flags for Cloud EPG */
+public final class DefaultCloudEpgFlags implements CloudEpgFlags {
+
+ private String mThirdPartyEpgInputCsv =
+ "com.google.android.tv/.tuner.tvinput.TunerTvInputService,"
+ + "com.technicolor.skipper.tuner/.tvinput.TunerTvInputService,"
+ + "com.silicondust.view/.tif.SDTvInputService";
+
+ @Override
+ public boolean compiled() {
+ return true;
+ }
+
+ @Override
+ public boolean supportedRegion() {
+ return false;
+ }
+
+ public void setThirdPartyEpgInputCsv(String value) {
+ mThirdPartyEpgInputCsv = value;
+ }
+
+ @Override
+ public String thirdPartyEpgInputsCsv() {
+ return mThirdPartyEpgInputCsv;
+ }
+}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java
new file mode 100644
index 0000000..8d8c584
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultConcurrentDvrPlaybackFlags.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import com.android.tv.common.flags.ConcurrentDvrPlaybackFlags;
+
+/** Default flags for Concurrent DVR Playback */
+public final class DefaultConcurrentDvrPlaybackFlags implements ConcurrentDvrPlaybackFlags {
+
+ @Override
+ public boolean compiled() {
+ return true;
+ }
+
+ @Override
+ public boolean enabled() {
+ return false;
+ }
+
+ @Override
+ public boolean onTuneUsesRecording() {
+ 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
new file mode 100644
index 0000000..4935236
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultFlagsModule.java
@@ -0,0 +1,60 @@
+/*
+ * 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.TunerFlags;
+import com.android.tv.common.flags.UiFlags;
+
+/** Provides default flags. */
+@Module
+public class DefaultFlagsModule {
+
+ @Provides
+ @Reusable
+ BackendKnobsFlags provideBackendKnobsFlags() {
+ return new DefaultBackendKnobsFlags();
+ }
+
+ @Provides
+ @Reusable
+ CloudEpgFlags provideCloudEpgFlags() {
+ return new DefaultCloudEpgFlags();
+ }
+
+ @Provides
+ @Reusable
+ ConcurrentDvrPlaybackFlags provideConcurrentDvrPlaybackFlags() {
+ return new DefaultConcurrentDvrPlaybackFlags();
+ }
+
+ @Provides
+ @Reusable
+ TunerFlags provideTunerFlags() {
+ return new DefaultTunerFlags();
+ }
+
+ @Provides
+ @Reusable
+ UiFlags provideUiFlags() {
+ return new DefaultUiFlags();
+ }
+}
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java
new file mode 100644
index 0000000..195953b
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultTunerFlags.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import com.android.tv.common.flags.TunerFlags;
+
+/** Default Flags for Tuner */
+public class DefaultTunerFlags implements TunerFlags {
+
+ @Override
+ public boolean compiled() {
+ return true;
+ }
+
+ @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
new file mode 100644
index 0000000..fce4585
--- /dev/null
+++ b/common/src/com/android/tv/common/flags/impl/DefaultUiFlags.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import com.android.tv.common.flags.UiFlags;
+
+/** Default Flags for Live TV UI */
+public class DefaultUiFlags implements UiFlags {
+
+ @Override
+ public boolean compiled() {
+ return true;
+ }
+
+ @Override
+ public boolean uhideLauncher() {
+ return false;
+ }
+
+ @Override
+ public boolean useLeanbackPinPicker() {
+ return false;
+ }
+
+ @Override
+ public long maxHistoryDays() {
+ return 7;
+ }
+}
diff --git a/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java
index 8b45a73..0fb864b 100644
--- a/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java
+++ b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java
@@ -217,6 +217,7 @@
}
} catch (IllegalArgumentException e) {
// In rare cases, storage status change was not notified yet.
+ Log.w(TAG, "Error getting Dvr Storage Status.", e);
SoftPreconditions.checkState(false);
return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT;
}
@@ -246,7 +247,7 @@
StatFs statFs = new StatFs(storageMountedDir.toString());
storageMountedCapacity = statFs.getTotalBytes();
} catch (IllegalArgumentException e) {
- Log.e(TAG, "Storage mount status was changed.");
+ Log.w(TAG, "Storage mount status was changed.", e);
storageMounted = false;
storageMountedDir = null;
}
diff --git a/common/src/com/android/tv/common/singletons/HasSingletons.java b/common/src/com/android/tv/common/singletons/HasSingletons.java
new file mode 100644
index 0000000..193aed3
--- /dev/null
+++ b/common/src/com/android/tv/common/singletons/HasSingletons.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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.singletons;
+
+import android.content.Context;
+
+/**
+ * A type that can know about and supply a singleton, typically a type t such as an android activity
+ * or application.
+ */
+public interface HasSingletons<C> {
+
+ @SuppressWarnings("unchecked") // injection
+ static <C> C get(Class<C> clazz, Context context) {
+ return ((HasSingletons<C>) context).singletons();
+ }
+
+ /** Returns the strongly typed singleton. */
+ C singletons();
+}
diff --git a/src/com/android/tv/util/Filter.java b/common/src/com/android/tv/common/singletons/HasTvInputId.java
similarity index 64%
copy from src/com/android/tv/util/Filter.java
copy to common/src/com/android/tv/common/singletons/HasTvInputId.java
index 3e24a49..4bc0a21 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/common/src/com/android/tv/common/singletons/HasTvInputId.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv.common.singletons;
-package com.android.tv.util;
+/**
+ * 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.
+ */
+public interface HasTvInputId {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ String getEmbeddedTunerInputId();
}
diff --git a/common/src/com/android/tv/common/support/README.md b/common/src/com/android/tv/common/support/README.md
new file mode 100644
index 0000000..67993f3
--- /dev/null
+++ b/common/src/com/android/tv/common/support/README.md
@@ -0,0 +1,8 @@
+# Support Libraries
+
+Packages here are destined to become support libraries.
+
+Each package should be self contained and only have dependencies on public libraries.
+
+It if becomes clear a package should not or will not be part of a support library move it to a
+different location.
\ No newline at end of file
diff --git a/common/src/com/android/tv/common/support/tis/BaseTvInputService.java b/common/src/com/android/tv/common/support/tis/BaseTvInputService.java
new file mode 100644
index 0000000..7791550
--- /dev/null
+++ b/common/src/com/android/tv/common/support/tis/BaseTvInputService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 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.support.tis;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import com.android.tv.common.support.tis.TifSession.TifSessionFactory;
+
+/** Abstract TVInputService. */
+public abstract class BaseTvInputService extends TvInputService {
+
+ private static final IntentFilter INTENT_FILTER = new IntentFilter();
+
+ static {
+ INTENT_FILTER.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
+ INTENT_FILTER.addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED);
+ }
+
+ @VisibleForTesting
+ protected final BroadcastReceiver broadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED:
+ case TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED:
+ for (Session session : getSessionManager().getSessions()) {
+ if (session instanceof WrappedSession) {
+ ((WrappedSession) session).onParentalControlsChanged();
+ }
+ }
+ break;
+ default:
+ // do nothing
+ }
+ }
+ };
+
+ @Nullable
+ @Override
+ public final WrappedSession onCreateSession(String inputId) {
+ SessionManager sessionManager = getSessionManager();
+ if (sessionManager.canCreateNewSession()) {
+ WrappedSession session =
+ new WrappedSession(
+ getApplicationContext(),
+ sessionManager,
+ getTifSessionFactory(),
+ inputId);
+ sessionManager.addSession(session);
+ return session;
+ }
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ registerReceiver(broadcastReceiver, INTENT_FILTER);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(broadcastReceiver);
+ }
+
+ protected abstract TifSessionFactory getTifSessionFactory();
+
+ protected abstract SessionManager getSessionManager();
+}
diff --git a/common/src/com/android/tv/common/support/tis/SessionManager.java b/common/src/com/android/tv/common/support/tis/SessionManager.java
new file mode 100644
index 0000000..5eeebc8
--- /dev/null
+++ b/common/src/com/android/tv/common/support/tis/SessionManager.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.support.tis;
+
+import android.media.tv.TvInputService.Session;
+import com.google.common.collect.ImmutableSet;
+
+/** Manages the number of concurrent sessions, keeping track of when sessions are released. */
+public interface SessionManager {
+
+ void removeSession(Session session);
+
+ void addSession(Session session);
+
+ boolean canCreateNewSession();
+
+ ImmutableSet<Session> getSessions();
+}
diff --git a/common/src/com/android/tv/common/support/tis/SimpleSessionManager.java b/common/src/com/android/tv/common/support/tis/SimpleSessionManager.java
new file mode 100644
index 0000000..f0636cc
--- /dev/null
+++ b/common/src/com/android/tv/common/support/tis/SimpleSessionManager.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 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.support.tis;
+
+import android.media.tv.TvInputService.Session;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.VisibleForTesting;
+import android.util.ArraySet;
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Set;
+
+/** A simple session manager that allows a maximum number of concurrent session. */
+public final class SimpleSessionManager implements SessionManager {
+
+ private final Set<Session> sessions;
+ private final int max;
+
+ public SimpleSessionManager(int max) {
+ this.max = max;
+ sessions = VERSION.SDK_INT >= VERSION_CODES.M ? new ArraySet<>() : new HashSet<>();
+ }
+
+ @Override
+ public void removeSession(Session session) {
+ sessions.remove(session);
+ }
+
+ @Override
+ public void addSession(Session session) {
+ sessions.add(session);
+ }
+
+ @Override
+ public boolean canCreateNewSession() {
+ return sessions.size() < max;
+ }
+
+ @Override
+ public ImmutableSet<Session> getSessions() {
+ return ImmutableSet.copyOf(sessions);
+ }
+
+ @VisibleForTesting
+ int getSessionCount() {
+ return sessions.size();
+ }
+}
diff --git a/common/src/com/android/tv/common/support/tis/TifSession.java b/common/src/com/android/tv/common/support/tis/TifSession.java
new file mode 100644
index 0000000..61cfe76
--- /dev/null
+++ b/common/src/com/android/tv/common/support/tis/TifSession.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 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.support.tis;
+
+import android.annotation.TargetApi;
+import android.media.PlaybackParams;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService.Session;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.FloatRange;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.Surface;
+import android.view.View;
+import java.util.List;
+
+/**
+ * Custom {@link android.media.tv.TvInputService.Session} class that uses delegation and a callback
+ * to separate it from the TvInputService for easier testing.
+ */
+public abstract class TifSession {
+
+ private final TifSessionCallbacks callback;
+
+ /**
+ * Creates TV Input Framework Session with the given callback.
+ *
+ * <p>The callback is used to pass notification to the actual {@link
+ * android.media.tv.TvInputService.Session}.
+ *
+ * <p>Pass a mock callback for tests.
+ */
+ protected TifSession(TifSessionCallbacks callback) {
+ this.callback = callback;
+ }
+
+ /**
+ * Called after this session had been created and the callback is attached.
+ *
+ * <p>Do not call notify methods in the constructor, instead call them here if needed at
+ * creation time. eg @{@link Session#notifyTimeShiftStatusChanged(int)}.
+ */
+ public void onSessionCreated() {}
+
+ /** @see Session#onRelease() */
+ public void onRelease() {}
+
+ /** @see Session#onSetSurface(Surface) */
+ public abstract boolean onSetSurface(@Nullable Surface surface);
+
+ /** @see Session#onSurfaceChanged(int, int, int) */
+ public abstract void onSurfaceChanged(int format, int width, int height);
+
+ /** @see Session#onSetStreamVolume(float) */
+ public abstract void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume);
+
+ /** @see Session#onTune(Uri) */
+ public abstract boolean onTune(Uri channelUri);
+
+ /** @see Session#onSetCaptionEnabled(boolean) */
+ public abstract void onSetCaptionEnabled(boolean enabled);
+
+ /** @see Session#onUnblockContent(TvContentRating) */
+ public abstract void onUnblockContent(TvContentRating unblockedRating);
+
+ /** @see Session#onTimeShiftGetCurrentPosition() */
+ @TargetApi(Build.VERSION_CODES.M)
+ public long onTimeShiftGetCurrentPosition() {
+ return TvInputManager.TIME_SHIFT_INVALID_TIME;
+ }
+
+ /** @see Session#onTimeShiftGetStartPosition() */
+ @TargetApi(Build.VERSION_CODES.M)
+ public long onTimeShiftGetStartPosition() {
+ return TvInputManager.TIME_SHIFT_INVALID_TIME;
+ }
+
+ /** @see Session#onTimeShiftPause() */
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onTimeShiftPause() {}
+
+ /** @see Session#onTimeShiftResume() */
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onTimeShiftResume() {}
+
+ /** @see Session#onTimeShiftSeekTo(long) */
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onTimeShiftSeekTo(long timeMs) {}
+
+ /** @see Session#onTimeShiftSetPlaybackParams(PlaybackParams) */
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onTimeShiftSetPlaybackParams(PlaybackParams params) {}
+
+ public void onParentalControlsChanged() {}
+
+ /** @see Session#notifyChannelRetuned(Uri) */
+ public final void notifyChannelRetuned(final Uri channelUri) {
+ callback.notifyChannelRetuned(channelUri);
+ }
+
+ /** @see Session#notifyTracksChanged(List) */
+ public final void notifyTracksChanged(final List<TvTrackInfo> tracks) {
+ callback.notifyTracksChanged(tracks);
+ }
+
+ /** @see Session#notifyTrackSelected(int, String) */
+ public final void notifyTrackSelected(final int type, final String trackId) {
+ callback.notifyTrackSelected(type, trackId);
+ }
+
+ /** @see Session#notifyVideoAvailable() */
+ public final void notifyVideoAvailable() {
+ callback.notifyVideoAvailable();
+ }
+
+ /** @see Session#notifyVideoUnavailable(int) */
+ public final void notifyVideoUnavailable(final int reason) {
+ callback.notifyVideoUnavailable(reason);
+ }
+
+ /** @see Session#notifyContentAllowed() */
+ public final void notifyContentAllowed() {
+ callback.notifyContentAllowed();
+ }
+
+ /** @see Session#notifyContentBlocked(TvContentRating) */
+ public final void notifyContentBlocked(@NonNull final TvContentRating rating) {
+ callback.notifyContentBlocked(rating);
+ }
+
+ /** @see Session#notifyTimeShiftStatusChanged(int) */
+ @TargetApi(VERSION_CODES.M)
+ public final void notifyTimeShiftStatusChanged(final int status) {
+ callback.notifyTimeShiftStatusChanged(status);
+ }
+
+ /** @see Session#setOverlayViewEnabled(boolean) */
+ public void setOverlayViewEnabled(boolean enabled) {
+ callback.setOverlayViewEnabled(enabled);
+ }
+
+ /** @see Session#onCreateOverlayView() */
+ public View onCreateOverlayView() {
+ return null;
+ }
+
+ /** @see Session#onOverlayViewSizeChanged(int, int) */
+ public void onOverlayViewSizeChanged(int width, int height) {}
+
+ /**
+ * Callbacks used to notify the {@link android.media.tv.TvInputService.Session}.
+ *
+ * <p>This is implemented internally by {@link WrappedSession}, and can be mocked for tests.
+ */
+ public interface TifSessionCallbacks {
+ /** @see Session#notifyChannelRetuned(Uri) */
+ void notifyChannelRetuned(final Uri channelUri);
+ /** @see Session#notifyTracksChanged(List) */
+ void notifyTracksChanged(final List<TvTrackInfo> tracks);
+ /** @see Session#notifyTrackSelected(int, String) */
+ void notifyTrackSelected(final int type, final String trackId);
+ /** @see Session#notifyVideoAvailable() */
+ void notifyVideoAvailable();
+ /** @see Session#notifyVideoUnavailable(int) */
+ void notifyVideoUnavailable(final int reason);
+ /** @see Session#notifyContentAllowed() */
+ void notifyContentAllowed();
+ /** @see Session#notifyContentBlocked(TvContentRating) */
+ void notifyContentBlocked(@NonNull final TvContentRating rating);
+ /** @see Session#notifyTimeShiftStatusChanged(int) */
+ @TargetApi(VERSION_CODES.M)
+ void notifyTimeShiftStatusChanged(final int status);
+ /** @see Session#setOverlayViewEnabled(boolean) */
+ void setOverlayViewEnabled(boolean enabled);
+ }
+
+ /**
+ * Creates a {@link TifSession}.
+ *
+ * <p>This is used by {@link WrappedSession} to create the desired {@code TifSession}. Should be
+ * used with <a href="http://go/autofactory">go/autofactory</a>.
+ */
+ public interface TifSessionFactory {
+ TifSession create(TifSessionCallbacks callbacks, String inputId);
+ }
+}
diff --git a/common/src/com/android/tv/common/support/tis/WrappedSession.java b/common/src/com/android/tv/common/support/tis/WrappedSession.java
new file mode 100644
index 0000000..f4a71dd
--- /dev/null
+++ b/common/src/com/android/tv/common/support/tis/WrappedSession.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 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.support.tis;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.media.PlaybackParams;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputService.Session;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.FloatRange;
+import android.support.annotation.Nullable;
+import android.view.Surface;
+import android.view.View;
+import com.android.tv.common.support.tis.TifSession.TifSessionCallbacks;
+import com.android.tv.common.support.tis.TifSession.TifSessionFactory;
+
+/**
+ * Delegates all call to a {@link TifSession} and removes the session from the {@link
+ * SessionManager} when {@link Session#onRelease()} is called.
+ */
+final class WrappedSession extends Session implements TifSessionCallbacks {
+
+ private final SessionManager listener;
+ private final TifSession delegate;
+
+ WrappedSession(
+ Context context,
+ SessionManager sessionManager,
+ TifSessionFactory sessionFactory,
+ String inputId) {
+ super(context);
+ this.listener = sessionManager;
+ this.delegate = sessionFactory.create(this, inputId);
+ }
+
+ @Override
+ public void onRelease() {
+ delegate.onRelease();
+ listener.removeSession(this);
+ }
+
+ @Override
+ public boolean onSetSurface(@Nullable Surface surface) {
+ return delegate.onSetSurface(surface);
+ }
+
+ @Override
+ public void onSurfaceChanged(int format, int width, int height) {
+ delegate.onSurfaceChanged(format, width, height);
+ }
+
+ @Override
+ public void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume) {
+ delegate.onSetStreamVolume(volume);
+ }
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ return delegate.onTune(channelUri);
+ }
+
+ @Override
+ public void onSetCaptionEnabled(boolean enabled) {
+ delegate.onSetCaptionEnabled(enabled);
+ }
+
+ @Override
+ public void onUnblockContent(TvContentRating unblockedRating) {
+ delegate.onUnblockContent(unblockedRating);
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public long onTimeShiftGetCurrentPosition() {
+ return delegate.onTimeShiftGetCurrentPosition();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public long onTimeShiftGetStartPosition() {
+ return delegate.onTimeShiftGetStartPosition();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onTimeShiftPause() {
+ delegate.onTimeShiftPause();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onTimeShiftResume() {
+ delegate.onTimeShiftResume();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onTimeShiftSeekTo(long timeMs) {
+ delegate.onTimeShiftSeekTo(timeMs);
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
+ delegate.onTimeShiftSetPlaybackParams(params);
+ }
+
+ public void onParentalControlsChanged() {
+ delegate.onParentalControlsChanged();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public void notifyTimeShiftStatusChanged(int status) {
+ // TODO(nchalko): why is the required for call from TisSession.onSessionCreated to work
+ super.notifyTimeShiftStatusChanged(status);
+ }
+
+ @Override
+ public void setOverlayViewEnabled(boolean enabled) {
+ super.setOverlayViewEnabled(enabled);
+ }
+
+ @Override
+ public View onCreateOverlayView() {
+ return delegate.onCreateOverlayView();
+ }
+
+ @Override
+ public void onOverlayViewSizeChanged(int width, int height) {
+ delegate.onOverlayViewSizeChanged(width, height);
+ }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/SetupActivity.java b/common/src/com/android/tv/common/ui/setup/SetupActivity.java
index 67418ce..1a3ddbd 100644
--- a/common/src/com/android/tv/common/ui/setup/SetupActivity.java
+++ b/common/src/com/android/tv/common/ui/setup/SetupActivity.java
@@ -16,7 +16,6 @@
package com.android.tv.common.ui.setup;
-import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
@@ -27,10 +26,10 @@
import android.transition.Transition;
import android.transition.TransitionInflater;
import android.view.View;
-import android.view.ViewTreeObserver.OnPreDrawListener;
import com.android.tv.common.R;
import com.android.tv.common.WeakHandler;
import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
+import dagger.android.DaggerActivity;
/**
* Setup activity for onboarding screens or TIS.
@@ -38,7 +37,7 @@
* <p>The inherited class should add theme {@code Theme.Setup.GuidedStep} to its definition in
* AndroidManifest.xml.
*/
-public abstract class SetupActivity extends Activity implements OnActionClickListener {
+public abstract class SetupActivity extends DaggerActivity implements OnActionClickListener {
private static final int MSG_EXECUTE_ACTION = 1;
private boolean mShowInitialFragment = true;
@@ -55,23 +54,7 @@
// Show initial fragment only when the saved state is not restored, because the last
// fragment is restored if savesInstanceState is not null.
if (savedInstanceState == null) {
- // This is the workaround to show the first fragment with delay to show the fragment
- // enter transition. See http://b/26255145
- getWindow()
- .getDecorView()
- .getViewTreeObserver()
- .addOnPreDrawListener(
- new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getWindow()
- .getDecorView()
- .getViewTreeObserver()
- .removeOnPreDrawListener(this);
- showInitialFragment();
- return true;
- }
- });
+ showInitialFragment();
} else {
mShowInitialFragment = false;
}
diff --git a/common/src/com/android/tv/common/util/CommonUtils.java b/common/src/com/android/tv/common/util/CommonUtils.java
index 305431d..4513a87 100644
--- a/common/src/com/android/tv/common/util/CommonUtils.java
+++ b/common/src/com/android/tv/common/util/CommonUtils.java
@@ -138,14 +138,23 @@
return ISO_8601.get().format(new Date(timeMillis));
}
- /** Deletes a file or a directory. */
- public static void deleteDirOrFile(File fileOrDirectory) {
+ /**
+ * Deletes a file or a directory.
+ *
+ * @return <code>true</code> if and only if the file or directory is successfully deleted;
+ * <code>false</code> otherwise
+ */
+ public static boolean deleteDirOrFile(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
- for (File child : fileOrDirectory.listFiles()) {
- deleteDirOrFile(child);
+ File[] files = fileOrDirectory.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ deleteDirOrFile(child);
+ }
}
}
- fileOrDirectory.delete();
+ // If earlier deletes failed this will also
+ return fileOrDirectory.delete();
}
public static boolean isRoboTest() {
diff --git a/common/src/com/android/tv/common/util/LocationUtils.java b/common/src/com/android/tv/common/util/LocationUtils.java
index 5315529..ee5119e 100644
--- a/common/src/com/android/tv/common/util/LocationUtils.java
+++ b/common/src/com/android/tv/common/util/LocationUtils.java
@@ -34,14 +34,20 @@
import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
/** A utility class to get the current location. */
public class LocationUtils {
private static final String TAG = "LocationUtils";
private static final boolean DEBUG = false;
+ private static final Set<OnUpdateAddressListener> sOnUpdateAddressListeners =
+ Collections.synchronizedSet(new HashSet<>());
+
private static Context sApplicationContext;
private static Address sAddress;
private static String sCountry;
@@ -63,6 +69,39 @@
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.
+ *
+ * @return {@code true} if the job has been finished and the listener needs to be removed;
+ * {@code false} otherwise.
+ */
+ boolean onUpdateAddress(Address address);
+ }
+
+ /**
+ * Add an {@link OnUpdateAddressListener} instance.
+ *
+ * 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);
+ }
+
+ /**
+ * 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}.
+ */
+ public static void removeOnUpdateAddressListener(OnUpdateAddressListener listener) {
+ sOnUpdateAddressListeners.remove(listener);
+ }
+
/** Returns the current country. */
@NonNull
public static synchronized String getCurrentCountry(Context context) {
@@ -92,6 +131,17 @@
} catch (Exception e) {
// Do nothing
}
+ Set<OnUpdateAddressListener> listenersToRemove = new HashSet<>();
+ synchronized (sOnUpdateAddressListeners) {
+ for (OnUpdateAddressListener listener : sOnUpdateAddressListeners) {
+ if (listener.onUpdateAddress(sAddress)) {
+ listenersToRemove.add(listener);
+ }
+ }
+ for (OnUpdateAddressListener listener : listenersToRemove) {
+ removeOnUpdateAddressListener(listener);
+ }
+ }
} else {
if (DEBUG) Log.d(TAG, "No address returned");
}
diff --git a/common/src/com/android/tv/common/util/NetworkTrafficTags.java b/common/src/com/android/tv/common/util/NetworkTrafficTags.java
index 91f2bcd..3c94aed 100644
--- a/common/src/com/android/tv/common/util/NetworkTrafficTags.java
+++ b/common/src/com/android/tv/common/util/NetworkTrafficTags.java
@@ -43,19 +43,16 @@
@Override
public void execute(final @NonNull Runnable command) {
- // TODO(b/62038127): robolectric does not support lamdas in unbundled apps
- delegateExecutor.execute(
- new Runnable() {
- @Override
- public void run() {
- 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 8d409e5..ca1abdc 100644
--- a/common/src/com/android/tv/common/util/PermissionUtils.java
+++ b/common/src/com/android/tv/common/util/PermissionUtils.java
@@ -65,4 +65,9 @@
return context.checkSelfPermission("android.permission.INTERNET")
== PackageManager.PERMISSION_GRANTED;
}
+
+ public static boolean hasWriteExternalStorage(Context context) {
+ return context.checkSelfPermission("android.permission.WRITE_EXTERNAL_STORAGE")
+ == PackageManager.PERMISSION_GRANTED;
+ }
}
diff --git a/common/src/com/android/tv/common/util/StringUtils.java b/common/src/com/android/tv/common/util/StringUtils.java
index b946142..bc82620 100644
--- a/common/src/com/android/tv/common/util/StringUtils.java
+++ b/common/src/com/android/tv/common/util/StringUtils.java
@@ -31,4 +31,9 @@
}
return a.compareTo(b);
}
+
+ /** Returns {@code s} or {@code ""} if {@code s} is {@code null} */
+ public static final String nullToEmpty(String s) {
+ return s == null ? "" : s;
+ }
}
diff --git a/common/src/com/android/tv/common/util/SystemProperties.java b/common/src/com/android/tv/common/util/SystemProperties.java
index a9f18d4..6ac2907 100644
--- a/common/src/com/android/tv/common/util/SystemProperties.java
+++ b/common/src/com/android/tv/common/util/SystemProperties.java
@@ -40,6 +40,10 @@
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);
+
static {
updateSystemProperties();
}
diff --git a/jni/gen_jni.sh b/gradle.properties
old mode 100755
new mode 100644
similarity index 64%
rename from jni/gen_jni.sh
rename to gradle.properties
index c06b7b9..6208234
--- a/jni/gen_jni.sh
+++ b/gradle.properties
@@ -1,6 +1,4 @@
-#!/bin/bash
-#
-# Copyright (C) 2017 The Android Open Source Project
+# 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.
@@ -14,5 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+#
+# Experimental gradle configuration. This file may not be up to date.
+#
-javah -jni -classpath ../../bin/classes:../../../../../../prebuilts/sdk/current/public/android.jar -o tunertvinput_jni.h com.android.tv.tuner.TunerHal
+
+org.gradle.jvmargs=-Xmx6144m -XX:MaxPermSize=6144m -XX:+HeapDumpOnOutOfMemoryError
+
+org.gradle.daemon=true
+org.gradle.parallel=true
+org.gradle.configureondemand=true
\ No newline at end of file
diff --git a/jni/Android.bp b/jni/Android.bp
new file mode 100644
index 0000000..bbf2778
--- /dev/null
+++ b/jni/Android.bp
@@ -0,0 +1,30 @@
+//
+// 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.
+//
+
+cc_library_shared {
+ name: "libtunertvinput_jni",
+ srcs: [
+ "tunertvinput_jni.cpp",
+ "DvbManager.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ sdk_version: "23",
+ stl: "c++_static",
+ shared_libs: ["liblog"],
+}
diff --git a/jni/Android.mk b/jni/Android.mk
deleted file mode 100644
index cfc8623..0000000
--- a/jni/Android.mk
+++ /dev/null
@@ -1,30 +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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-# --------------------------------------------------------------
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libtunertvinput_jni
-LOCAL_SRC_FILES += tunertvinput_jni.cpp DvbManager.cpp
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_SDK_VERSION := 23
-LOCAL_NDK_STL_VARIANT := c++_static
-LOCAL_LDLIBS := -llog
-
-include $(BUILD_SHARED_LIBRARY)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/jni/DvbManager.cpp b/jni/DvbManager.cpp
index 8e51999..f9dff59 100644
--- a/jni/DvbManager.cpp
+++ b/jni/DvbManager.cpp
@@ -82,6 +82,37 @@
return false;
}
+// This function gets the signal strength from tuner.
+// Output can be:
+// -3 means File Descriptor invalid,
+// or DVB version is not supported,
+// or ERROR while communicate with hardware via ioctl.
+// int signal returns the raw signal strength value.
+int DvbManager::getSignalStrength() {
+ // TODO(b/74197177): add support for DVB V5.
+ if (mFeFd == -1 || mDvbApiVersion != DVB_API_VERSION3) {
+ return -3;
+ }
+ uint16_t strength = 0;
+ // ERROR code from ioctl can be:
+ // EBADF means fd is not a valid open file descriptor
+ // EFAULT means status points to invalid address
+ // ENOSIGNAL means there is no signal, thus no meaningful signal strength
+ // ENOSYS means function not available for this device
+ //
+ // The function used to communicate with tuner in DVB v3 is
+ // ioctl(fd, request, &strength)
+ // int fd is the File Descriptor, can't be -1
+ // int request is the request type,
+ // FE_READ_SIGNAL_STRENGTH for getting signal strength
+ // uint16_t *strength stores the strength value returned from tuner
+ if (ioctl(mFeFd, FE_READ_SIGNAL_STRENGTH, &strength) == -1) {
+ ALOGD("FE_READ_SIGNAL_STRENGTH failed, %s", strerror(errno));
+ return -3;
+ }
+ return strength;
+}
+
int DvbManager::tune(JNIEnv *env, jobject thiz,
const int frequency, const char *modulationStr, int timeout_ms) {
resetExceptFe();
diff --git a/jni/DvbManager.h b/jni/DvbManager.h
index 124fa94..b01113e 100644
--- a/jni/DvbManager.h
+++ b/jni/DvbManager.h
@@ -49,7 +49,7 @@
static const int DELIVERY_SYSTEM_ATSC =
com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_ATSC;
static const int DELIVERY_SYSTEM_DVBC =
- com_android_tv_tuner_TunerHal_DDELIVERY_SYSTEM_DVBC;
+ com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBC;
static const int DELIVERY_SYSTEM_DVBS =
com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS;
static const int DELIVERY_SYSTEM_DVBS2 =
@@ -85,6 +85,7 @@
void closeAllDvbPidFilter();
void setHasPendingTune(bool hasPendingTune);
int getDeliverySystemType(JNIEnv *env, jobject thiz);
+ int getSignalStrength();
private:
int openDvbFe(JNIEnv *env, jobject thiz);
diff --git a/jni/tunertvinput_jni.cpp b/jni/tunertvinput_jni.cpp
index 368e2d5..030f961 100644
--- a/jni/tunertvinput_jni.cpp
+++ b/jni/tunertvinput_jni.cpp
@@ -96,6 +96,23 @@
/*
* Class: com_android_tv_tuner_TunerHal
+ * Method: nativeGetSignalStrength
+ * Signature: (J)V
+ */
+JNIEXPORT int JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeGetSignalStrength(
+ JNIEnv *, jobject, jlong deviceId) {
+ std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+ if (it != sDvbManagers.end()) {
+ return it->second->getSignalStrength();
+ }
+ // If DvbManager can't be found,
+ // return -3 as signal strength not supported.
+ return -3;
+}
+
+/*
+ * Class: com_android_tv_tuner_TunerHal
* Method: nativeAddPidFilter
* Signature: (JII)V
*/
diff --git a/jni/tunertvinput_jni.h b/jni/tunertvinput_jni.h
old mode 100644
new mode 100755
index d299c30..36e631f
--- a/jni/tunertvinput_jni.h
+++ b/jni/tunertvinput_jni.h
@@ -17,20 +17,12 @@
#define com_android_tv_tuner_TunerHal_FILTER_TYPE_VIDEO 2L
#undef com_android_tv_tuner_TunerHal_FILTER_TYPE_PCR
#define com_android_tv_tuner_TunerHal_FILTER_TYPE_PCR 3L
-#undef com_android_tv_tuner_TunerHal_PID_PAT
-#define com_android_tv_tuner_TunerHal_PID_PAT 0L
-#undef com_android_tv_tuner_TunerHal_PID_ATSC_SI_BASE
-#define com_android_tv_tuner_TunerHal_PID_ATSC_SI_BASE 8187L
-#undef com_android_tv_tuner_TunerHal_DEFAULT_VSB_TUNE_TIMEOUT_MS
-#define com_android_tv_tuner_TunerHal_DEFAULT_VSB_TUNE_TIMEOUT_MS 2000L
-#undef com_android_tv_tuner_TunerHal_DEFAULT_QAM_TUNE_TIMEOUT_MS
-#define com_android_tv_tuner_TunerHal_DEFAULT_QAM_TUNE_TIMEOUT_MS 4000L
#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_UNDEFINED
#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_UNDEFINED 0L
#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_ATSC
#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_ATSC 1L
#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBC
-#define com_android_tv_tuner_TunerHal_DDELIVERY_SYSTEM_DVBC 2L
+#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBC 2L
#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS
#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS 3L
#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS2
@@ -39,29 +31,51 @@
#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT 5L
#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT2
#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT2 6L
+#undef com_android_tv_tuner_TunerHal_TUNER_TYPE_BUILT_IN
+#define com_android_tv_tuner_TunerHal_TUNER_TYPE_BUILT_IN 1L
+#undef com_android_tv_tuner_TunerHal_TUNER_TYPE_USB
+#define com_android_tv_tuner_TunerHal_TUNER_TYPE_USB 2L
+#undef com_android_tv_tuner_TunerHal_TUNER_TYPE_NETWORK
+#define com_android_tv_tuner_TunerHal_TUNER_TYPE_NETWORK 3L
+#undef com_android_tv_tuner_TunerHal_PID_PAT
+#define com_android_tv_tuner_TunerHal_PID_PAT 0L
+#undef com_android_tv_tuner_TunerHal_PID_ATSC_SI_BASE
+#define com_android_tv_tuner_TunerHal_PID_ATSC_SI_BASE 8187L
+#undef com_android_tv_tuner_TunerHal_PID_DVB_SDT
+#define com_android_tv_tuner_TunerHal_PID_DVB_SDT 17L
+#undef com_android_tv_tuner_TunerHal_PID_DVB_EIT
+#define com_android_tv_tuner_TunerHal_PID_DVB_EIT 18L
+#undef com_android_tv_tuner_TunerHal_DEFAULT_VSB_TUNE_TIMEOUT_MS
+#define com_android_tv_tuner_TunerHal_DEFAULT_VSB_TUNE_TIMEOUT_MS 2000L
+#undef com_android_tv_tuner_TunerHal_DEFAULT_QAM_TUNE_TIMEOUT_MS
+#define com_android_tv_tuner_TunerHal_DEFAULT_QAM_TUNE_TIMEOUT_MS 4000L
+#undef com_android_tv_tuner_TunerHal_BUILT_IN_TUNER_TYPE_LINUX_DVB
+#define com_android_tv_tuner_TunerHal_BUILT_IN_TUNER_TYPE_LINUX_DVB 1L
+#undef com_android_tv_tuner_TunerHal_BUILT_IN_TUNER_TYPE_ARCHER
+#define com_android_tv_tuner_TunerHal_BUILT_IN_TUNER_TYPE_ARCHER 100L
/*
* Class: com_android_tv_tuner_TunerHal
* Method: nativeFinalize
* Signature: (J)V
*/
-JNIEXPORT void JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeFinalize(JNIEnv *, jobject, jlong);
+JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeFinalize
+ (JNIEnv *, jobject, jlong);
/*
* Class: com_android_tv_tuner_TunerHal
* 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
+ (JNIEnv *, jobject, jlong, jint, jstring, jint);
/*
* Class: com_android_tv_tuner_TunerHal
* Method: nativeAddPidFilter
* Signature: (JII)V
*/
-JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeAddPidFilter(
- JNIEnv *, jobject, jlong, jint, jint);
+JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeAddPidFilter
+ (JNIEnv *, jobject, jlong, jint, jint);
/*
* Class: com_android_tv_tuner_TunerHal
@@ -69,24 +83,8 @@
* Signature: (J)V
*/
JNIEXPORT void JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeCloseAllPidFilters(JNIEnv *, jobject,
- jlong);
-
-/*
- * Class: com_android_tv_tuner_TunerHal
- * Method: nativeStopTune
- * Signature: (J)V
- */
-JNIEXPORT void JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeStopTune(JNIEnv *, jobject, jlong);
-
-/*
- * Class: com_android_tv_tuner_TunerHal
- * Method: nativeWriteInBuffer
- * Signature: (J[BI)I
- */
-JNIEXPORT jint JNICALL Java_com_android_tv_tuner_TunerHal_nativeWriteInBuffer(
- JNIEnv *, jobject, jlong, jbyteArray, jint);
+Java_com_android_tv_tuner_TunerHal_nativeCloseAllPidFilters
+ (JNIEnv *, jobject, jlong);
/*
* Class: com_android_tv_tuner_TunerHal
@@ -94,29 +92,43 @@
* Signature: (JZ)V
*/
JNIEXPORT void JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeSetHasPendingTune(JNIEnv *, jobject,
- jlong, jboolean);
+Java_com_android_tv_tuner_TunerHal_nativeSetHasPendingTune
+ (JNIEnv *, jobject, jlong, jboolean);
/*
* Class: com_android_tv_tuner_TunerHal
* Method: nativeGetDeliverySystemType
* Signature: (J)I
*/
-JNIEXPORT int JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType(JNIEnv *,
- jobject, jlong);
+JNIEXPORT jint JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType
+ (JNIEnv *, jobject, jlong);
-#ifdef __cplusplus
-}
-#endif
-#endif
-/* Header for class com_android_tv_tuner_TunerHal_FilterType */
+/*
+ * Class: com_android_tv_tuner_TunerHal
+ * Method: nativeGetSignalStrength
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeGetSignalStrength
+ (JNIEnv *, jobject, jlong);
-#ifndef _Included_com_android_tv_tuner_TunerHal_FilterType
-#define _Included_com_android_tv_tuner_TunerHal_FilterType
-#ifdef __cplusplus
-extern "C" {
-#endif
+/*
+ * Class: com_android_tv_tuner_TunerHal
+ * Method: nativeStopTune
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeStopTune
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_android_tv_tuner_TunerHal
+ * Method: nativeWriteInBuffer
+ * Signature: (J[BI)I
+ */
+JNIEXPORT jint JNICALL Java_com_android_tv_tuner_TunerHal_nativeWriteInBuffer
+ (JNIEnv *, jobject, jlong, jbyteArray, jint);
+
#ifdef __cplusplus
}
#endif
diff --git a/libs/Android.bp b/libs/Android.bp
new file mode 100644
index 0000000..fea9487
--- /dev/null
+++ b/libs/Android.bp
@@ -0,0 +1,165 @@
+// 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.
+
+java_import {
+ name: "tv-auto-factory-jar",
+ jars: ["auto-factory-1.0-beta2.jar"],
+ host_supported: true,
+ sdk_version: "current",
+}
+
+java_plugin {
+ name: "tv-auto-factory",
+ static_libs: [
+ "jsr330",
+ "tv-auto-factory-jar",
+ "tv-guava-jre-jar",
+ "tv-javawriter-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"],
+ host_supported: true,
+ sdk_version: "current",
+}
+
+java_plugin {
+ name: "tv-auto-value",
+ static_libs: [
+ "tv-auto-value-jar",
+ "tv-guava-jre-jar",
+ ],
+ processor_class: "com.google.auto.value.processor.AutoValueProcessor",
+}
+
+java_import {
+ name: "tv-error-prone-annotations-jar",
+ jars: ["error_prone_annotations-2.3.1.jar"],
+ sdk_version: "current",
+}
+
+java_import {
+ name: "tv-guava-jre-jar",
+ jars: ["guava-23.3-jre.jar"],
+ host_supported: true,
+ sdk_version: "current",
+}
+
+java_import {
+ name: "tv-guava-android-jar",
+ jars: ["guava-23.6-android.jar"],
+ sdk_version: "current",
+}
+
+java_import_host{
+ name: "tv-javawriter-jar",
+ jars: ["javawriter-2.5.1.jar"],
+}
+
+java_import {
+ name: "tv-javax-annotations-jar",
+ jars: ["javax.annotation-api-1.2.jar"],
+ host_supported: true,
+ sdk_version: "current",
+}
+
+
+android_library_import {
+ name: "tv-lib-exoplayer",
+ aars: ["exoplayer-r1.5.16.aar"],
+ sdk_version: "current",
+}
+
+android_library_import {
+ name: "tv-lib-exoplayer-v2-core",
+ aars: ["exoplayer-core-2.9.0.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",
+ ],
+}
+
+java_import {
+ name: "tv-lib-dagger",
+ jars: ["dagger-2.15.jar"],
+ host_supported: true,
+ sdk_version: "current",
+}
+
+java_plugin {
+ name: "tv-lib-dagger-compiler",
+ static_libs: [
+ "tv-lib-dagger-compiler-import",
+ "tv-lib-dagger-compiler-deps",
+ "jsr330",
+ "tv-lib-dagger",
+ ],
+ processor_class: "dagger.internal.codegen.ComponentProcessor",
+ generates_api: true,
+}
+
+android_library_import {
+ name: "tv-lib-dagger-android",
+ aars: ["dagger-android-2.15.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",
+ ],
+}
+
+java_plugin {
+ name: "tv-lib-dagger-android-processor",
+ static_libs: [
+ "tv-lib-dagger-android-processor-import",
+ "tv-lib-dagger-compiler-deps",
+ "jsr330",
+ "tv-lib-dagger",
+ ],
+ processor_class: "dagger.android.processor.AndroidProcessor",
+ generates_api: true,
+}
+
+java_import {
+ name: "tv-lib-truth",
+ jars: ["truth-0.36.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
new file mode 100644
index 0000000..ceaddac
--- /dev/null
+++ b/libs/auto-factory-1.0-beta2.jar
Binary files differ
diff --git a/libs/auto-value-1.5.2.jar b/libs/auto-value-1.5.2.jar
new file mode 100644
index 0000000..8ac0567
--- /dev/null
+++ b/libs/auto-value-1.5.2.jar
Binary files differ
diff --git a/libs/dagger-2.15.jar b/libs/dagger-2.15.jar
new file mode 100644
index 0000000..6d76688
--- /dev/null
+++ b/libs/dagger-2.15.jar
Binary files differ
diff --git a/libs/dagger-android-2.15.aar b/libs/dagger-android-2.15.aar
new file mode 100644
index 0000000..430294a
--- /dev/null
+++ b/libs/dagger-android-2.15.aar
Binary files differ
diff --git a/libs/dagger-android-jarimpl-2.15.jar b/libs/dagger-android-jarimpl-2.15.jar
new file mode 100644
index 0000000..7f7cd45
--- /dev/null
+++ b/libs/dagger-android-jarimpl-2.15.jar
Binary files differ
diff --git a/libs/dagger-android-processor-2.15.jar b/libs/dagger-android-processor-2.15.jar
new file mode 100644
index 0000000..3c7ac05
--- /dev/null
+++ b/libs/dagger-android-processor-2.15.jar
Binary files differ
diff --git a/libs/dagger-android-support-2.15.aar b/libs/dagger-android-support-2.15.aar
new file mode 100644
index 0000000..89a71a9
--- /dev/null
+++ b/libs/dagger-android-support-2.15.aar
Binary files differ
diff --git a/libs/dagger-android-support-jarimpl-2.15.jar b/libs/dagger-android-support-jarimpl-2.15.jar
new file mode 100644
index 0000000..d0ea01a
--- /dev/null
+++ b/libs/dagger-android-support-jarimpl-2.15.jar
Binary files differ
diff --git a/libs/dagger-compiler-2.15.jar b/libs/dagger-compiler-2.15.jar
new file mode 100644
index 0000000..e73110f
--- /dev/null
+++ b/libs/dagger-compiler-2.15.jar
Binary files differ
diff --git a/libs/dagger-producers-2.15.jar b/libs/dagger-producers-2.15.jar
new file mode 100644
index 0000000..f1dbb07
--- /dev/null
+++ b/libs/dagger-producers-2.15.jar
Binary files differ
diff --git a/libs/dagger-spi-2.15.jar b/libs/dagger-spi-2.15.jar
new file mode 100644
index 0000000..6e3156a
--- /dev/null
+++ b/libs/dagger-spi-2.15.jar
Binary files differ
diff --git a/libs/error_prone_annotations-2.3.1.jar b/libs/error_prone_annotations-2.3.1.jar
new file mode 100644
index 0000000..8a0efa3
--- /dev/null
+++ b/libs/error_prone_annotations-2.3.1.jar
Binary files differ
diff --git a/libs/exoplayer-core-2-SNAPHOT-20180114.aar b/libs/exoplayer-core-2-SNAPHOT-20180114.aar
deleted file mode 100644
index 90af2e6..0000000
--- a/libs/exoplayer-core-2-SNAPHOT-20180114.aar
+++ /dev/null
Binary files differ
diff --git a/libs/exoplayer-core-2.9.0.aar b/libs/exoplayer-core-2.9.0.aar
new file mode 100644
index 0000000..64c4f37
--- /dev/null
+++ b/libs/exoplayer-core-2.9.0.aar
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
new file mode 100644
index 0000000..b10bfbd
--- /dev/null
+++ b/libs/google-java-format-1.4-all-deps.jar
Binary files differ
diff --git a/libs/guava-23.3-jre.jar b/libs/guava-23.3-jre.jar
new file mode 100644
index 0000000..b13e275
--- /dev/null
+++ b/libs/guava-23.3-jre.jar
Binary files differ
diff --git a/libs/guava-23.5-jre.jar b/libs/guava-23.5-jre.jar
new file mode 100644
index 0000000..7e5f13a
--- /dev/null
+++ b/libs/guava-23.5-jre.jar
Binary files differ
diff --git a/libs/guava-23.6-android.jar b/libs/guava-23.6-android.jar
new file mode 100644
index 0000000..01180d2
--- /dev/null
+++ b/libs/guava-23.6-android.jar
Binary files differ
diff --git a/libs/javapoet-1.8.0.jar b/libs/javapoet-1.8.0.jar
new file mode 100644
index 0000000..6758b6d
--- /dev/null
+++ b/libs/javapoet-1.8.0.jar
Binary files differ
diff --git a/libs/javawriter-2.5.1.jar b/libs/javawriter-2.5.1.jar
new file mode 100644
index 0000000..4ec579e
--- /dev/null
+++ b/libs/javawriter-2.5.1.jar
Binary files differ
diff --git a/libs/javax.annotation-api-1.2.jar b/libs/javax.annotation-api-1.2.jar
new file mode 100644
index 0000000..9ab39ff
--- /dev/null
+++ b/libs/javax.annotation-api-1.2.jar
Binary files differ
diff --git a/libs/truth-0.36.jar b/libs/truth-0.36.jar
new file mode 100644
index 0000000..8174e4a
--- /dev/null
+++ b/libs/truth-0.36.jar
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_arrow_downward_white_36.png b/material_res/drawable-hdpi/quantum_ic_arrow_downward_white_36.png
new file mode 100644
index 0000000..76a57d1
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_arrow_downward_white_36.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_arrow_upward_white_36.png b/material_res/drawable-hdpi/quantum_ic_arrow_upward_white_36.png
new file mode 100644
index 0000000..ce42f36
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_arrow_upward_white_36.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_check_circle_white_48.png b/material_res/drawable-hdpi/quantum_ic_check_circle_white_48.png
new file mode 100644
index 0000000..4f96745
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_check_circle_white_48.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_developer_mode_tv_white_48.png b/material_res/drawable-hdpi/quantum_ic_developer_mode_tv_white_48.png
new file mode 100644
index 0000000..0d9bf8b
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_developer_mode_tv_white_48.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_error_white_48.png b/material_res/drawable-hdpi/quantum_ic_error_white_48.png
new file mode 100644
index 0000000..abe2573
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_error_white_48.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_signal_cellular_0_bar_white_24.png b/material_res/drawable-hdpi/quantum_ic_signal_cellular_0_bar_white_24.png
new file mode 100644
index 0000000..c7e8848
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_signal_cellular_0_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_signal_cellular_1_bar_white_24.png b/material_res/drawable-hdpi/quantum_ic_signal_cellular_1_bar_white_24.png
new file mode 100644
index 0000000..3b79370
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_signal_cellular_1_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_signal_cellular_2_bar_white_24.png b/material_res/drawable-hdpi/quantum_ic_signal_cellular_2_bar_white_24.png
new file mode 100644
index 0000000..bf736dc
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_signal_cellular_2_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_signal_cellular_3_bar_white_24.png b/material_res/drawable-hdpi/quantum_ic_signal_cellular_3_bar_white_24.png
new file mode 100644
index 0000000..8bee424
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_signal_cellular_3_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_signal_cellular_4_bar_white_24.png b/material_res/drawable-hdpi/quantum_ic_signal_cellular_4_bar_white_24.png
new file mode 100644
index 0000000..1765e94
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_signal_cellular_4_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_warning_white_18.png b/material_res/drawable-hdpi/quantum_ic_warning_white_18.png
new file mode 100644
index 0000000..7520b79
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_warning_white_18.png
Binary files differ
diff --git a/material_res/drawable-hdpi/quantum_ic_warning_white_96.png b/material_res/drawable-hdpi/quantum_ic_warning_white_96.png
new file mode 100644
index 0000000..88c2232
--- /dev/null
+++ b/material_res/drawable-hdpi/quantum_ic_warning_white_96.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_arrow_downward_white_36.png b/material_res/drawable-mdpi/quantum_ic_arrow_downward_white_36.png
new file mode 100644
index 0000000..dbfb7ab
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_arrow_downward_white_36.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_arrow_upward_white_36.png b/material_res/drawable-mdpi/quantum_ic_arrow_upward_white_36.png
new file mode 100644
index 0000000..c39725c
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_arrow_upward_white_36.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_check_circle_white_48.png b/material_res/drawable-mdpi/quantum_ic_check_circle_white_48.png
new file mode 100644
index 0000000..d36d696
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_check_circle_white_48.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_developer_mode_tv_white_48.png b/material_res/drawable-mdpi/quantum_ic_developer_mode_tv_white_48.png
new file mode 100644
index 0000000..976a44b
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_developer_mode_tv_white_48.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_error_white_48.png b/material_res/drawable-mdpi/quantum_ic_error_white_48.png
new file mode 100644
index 0000000..9829698
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_error_white_48.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_signal_cellular_0_bar_white_24.png b/material_res/drawable-mdpi/quantum_ic_signal_cellular_0_bar_white_24.png
new file mode 100644
index 0000000..a0c5d41
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_signal_cellular_0_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_signal_cellular_1_bar_white_24.png b/material_res/drawable-mdpi/quantum_ic_signal_cellular_1_bar_white_24.png
new file mode 100644
index 0000000..0e70e98
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_signal_cellular_1_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_signal_cellular_2_bar_white_24.png b/material_res/drawable-mdpi/quantum_ic_signal_cellular_2_bar_white_24.png
new file mode 100644
index 0000000..eb2124d
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_signal_cellular_2_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_signal_cellular_3_bar_white_24.png b/material_res/drawable-mdpi/quantum_ic_signal_cellular_3_bar_white_24.png
new file mode 100644
index 0000000..a2045c3
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_signal_cellular_3_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_signal_cellular_4_bar_white_24.png b/material_res/drawable-mdpi/quantum_ic_signal_cellular_4_bar_white_24.png
new file mode 100644
index 0000000..8d76fd4
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_signal_cellular_4_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_warning_white_18.png b/material_res/drawable-mdpi/quantum_ic_warning_white_18.png
new file mode 100644
index 0000000..1f05517
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_warning_white_18.png
Binary files differ
diff --git a/material_res/drawable-mdpi/quantum_ic_warning_white_96.png b/material_res/drawable-mdpi/quantum_ic_warning_white_96.png
new file mode 100644
index 0000000..8683a2e
--- /dev/null
+++ b/material_res/drawable-mdpi/quantum_ic_warning_white_96.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_arrow_downward_white_36.png b/material_res/drawable-xhdpi/quantum_ic_arrow_downward_white_36.png
new file mode 100644
index 0000000..225e4e5
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_arrow_downward_white_36.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_arrow_upward_white_36.png b/material_res/drawable-xhdpi/quantum_ic_arrow_upward_white_36.png
new file mode 100644
index 0000000..d7b27da
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_arrow_upward_white_36.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_check_circle_white_48.png b/material_res/drawable-xhdpi/quantum_ic_check_circle_white_48.png
new file mode 100644
index 0000000..2c6e474
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_check_circle_white_48.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_developer_mode_tv_white_48.png b/material_res/drawable-xhdpi/quantum_ic_developer_mode_tv_white_48.png
new file mode 100644
index 0000000..1336147
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_developer_mode_tv_white_48.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_error_white_48.png b/material_res/drawable-xhdpi/quantum_ic_error_white_48.png
new file mode 100644
index 0000000..830fb7e
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_error_white_48.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_signal_cellular_0_bar_white_24.png b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_0_bar_white_24.png
new file mode 100644
index 0000000..26d6bff
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_0_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_signal_cellular_1_bar_white_24.png b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_1_bar_white_24.png
new file mode 100644
index 0000000..3b7f7ad
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_1_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_signal_cellular_2_bar_white_24.png b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_2_bar_white_24.png
new file mode 100644
index 0000000..b80307c
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_2_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_signal_cellular_3_bar_white_24.png b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_3_bar_white_24.png
new file mode 100644
index 0000000..1ccc977
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_3_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_signal_cellular_4_bar_white_24.png b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_4_bar_white_24.png
new file mode 100644
index 0000000..62501b0
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_signal_cellular_4_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_warning_white_18.png b/material_res/drawable-xhdpi/quantum_ic_warning_white_18.png
new file mode 100644
index 0000000..55c6843
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_warning_white_18.png
Binary files differ
diff --git a/material_res/drawable-xhdpi/quantum_ic_warning_white_96.png b/material_res/drawable-xhdpi/quantum_ic_warning_white_96.png
new file mode 100644
index 0000000..23e6d93
--- /dev/null
+++ b/material_res/drawable-xhdpi/quantum_ic_warning_white_96.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_arrow_downward_white_36.png b/material_res/drawable-xxhdpi/quantum_ic_arrow_downward_white_36.png
new file mode 100644
index 0000000..0502223
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_arrow_downward_white_36.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_arrow_upward_white_36.png b/material_res/drawable-xxhdpi/quantum_ic_arrow_upward_white_36.png
new file mode 100644
index 0000000..eceb34c
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_arrow_upward_white_36.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_check_circle_white_48.png b/material_res/drawable-xxhdpi/quantum_ic_check_circle_white_48.png
new file mode 100644
index 0000000..980d10b
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_check_circle_white_48.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_developer_mode_tv_white_48.png b/material_res/drawable-xxhdpi/quantum_ic_developer_mode_tv_white_48.png
new file mode 100644
index 0000000..defa1bf
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_developer_mode_tv_white_48.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_error_white_48.png b/material_res/drawable-xxhdpi/quantum_ic_error_white_48.png
new file mode 100644
index 0000000..c834952
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_error_white_48.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_0_bar_white_24.png b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_0_bar_white_24.png
new file mode 100644
index 0000000..2e200fb
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_0_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_1_bar_white_24.png b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_1_bar_white_24.png
new file mode 100644
index 0000000..215f9b7
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_1_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_2_bar_white_24.png b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_2_bar_white_24.png
new file mode 100644
index 0000000..c8c0ebf
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_2_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_3_bar_white_24.png b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_3_bar_white_24.png
new file mode 100644
index 0000000..8377811
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_3_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_4_bar_white_24.png b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_4_bar_white_24.png
new file mode 100644
index 0000000..b191b9d
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_signal_cellular_4_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_warning_white_18.png b/material_res/drawable-xxhdpi/quantum_ic_warning_white_18.png
new file mode 100644
index 0000000..fb079e9
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_warning_white_18.png
Binary files differ
diff --git a/material_res/drawable-xxhdpi/quantum_ic_warning_white_96.png b/material_res/drawable-xxhdpi/quantum_ic_warning_white_96.png
new file mode 100644
index 0000000..064cd51
--- /dev/null
+++ b/material_res/drawable-xxhdpi/quantum_ic_warning_white_96.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_arrow_downward_white_36.png b/material_res/drawable-xxxhdpi/quantum_ic_arrow_downward_white_36.png
new file mode 100644
index 0000000..fe0ecc6
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_arrow_downward_white_36.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_arrow_upward_white_36.png b/material_res/drawable-xxxhdpi/quantum_ic_arrow_upward_white_36.png
new file mode 100644
index 0000000..5e61c3d
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_arrow_upward_white_36.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_check_circle_white_48.png b/material_res/drawable-xxxhdpi/quantum_ic_check_circle_white_48.png
new file mode 100644
index 0000000..60463c5
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_check_circle_white_48.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_developer_mode_tv_white_48.png b/material_res/drawable-xxxhdpi/quantum_ic_developer_mode_tv_white_48.png
new file mode 100644
index 0000000..b316f1d
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_developer_mode_tv_white_48.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_error_white_48.png b/material_res/drawable-xxxhdpi/quantum_ic_error_white_48.png
new file mode 100644
index 0000000..ad4a474
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_error_white_48.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_0_bar_white_24.png b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_0_bar_white_24.png
new file mode 100644
index 0000000..f805132
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_0_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_1_bar_white_24.png b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_1_bar_white_24.png
new file mode 100644
index 0000000..42debcc
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_1_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_2_bar_white_24.png b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_2_bar_white_24.png
new file mode 100644
index 0000000..6a7f320
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_2_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_3_bar_white_24.png b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_3_bar_white_24.png
new file mode 100644
index 0000000..706da32
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_3_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_4_bar_white_24.png b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_4_bar_white_24.png
new file mode 100644
index 0000000..4a3ac28
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_signal_cellular_4_bar_white_24.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_warning_white_18.png b/material_res/drawable-xxxhdpi/quantum_ic_warning_white_18.png
new file mode 100644
index 0000000..807b9fa
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_warning_white_18.png
Binary files differ
diff --git a/material_res/drawable-xxxhdpi/quantum_ic_warning_white_96.png b/material_res/drawable-xxxhdpi/quantum_ic_warning_white_96.png
new file mode 100644
index 0000000..2439be1
--- /dev/null
+++ b/material_res/drawable-xxxhdpi/quantum_ic_warning_white_96.png
Binary files differ
diff --git a/open_source_project.README b/open_source_project.README
index 31532f0..897b8ee 100644
--- a/open_source_project.README
+++ b/open_source_project.README
@@ -7,7 +7,7 @@
https://source.android.com/source/building.html
(Developers using PDK can skip the step 1.)
2. Enable the feature PackageManager.FEATURE_LIVE_TV.
-3. Put this project under Android platform repository.
+3. Put this project under Android platform repository if required.
4. Include this package inside platform build.
5. Build the platform.
https://source.android.com/source/building.html
diff --git a/partner_support/Android.bp b/partner_support/Android.bp
new file mode 100644
index 0000000..4775fc1
--- /dev/null
+++ b/partner_support/Android.bp
@@ -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.
+//
+
+android_library {
+ name: "live-channels-partner-support",
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "system_current",
+ min_sdk_version: "23",
+
+ resource_dirs: ["res"],
+
+ static_libs: ["android-support-annotations"],
+
+ libs: ["tv-auto-value-jar"],
+
+ plugins: ["tv-auto-value"],
+
+}
diff --git a/partner_support/Android.mk b/partner_support/Android.mk
deleted file mode 100644
index 8306921..0000000
--- a/partner_support/Android.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include all java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := live-channels-partner-support
-LOCAL_MODULE_CLASS := STATIC_JAVA_LIBRARIES
-LOCAL_MODULE_TAGS := optional
-LOCAL_SDK_VERSION := system_current
-LOCAL_MIN_SDK_VERSION := 23
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-annotations
-
-include $(LOCAL_PATH)/buildconfig.mk
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/partner_support/AndroidManifest.xml b/partner_support/AndroidManifest.xml
index 5a45f0d..45a693f 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="26" android:minSdkVersion="21"/>
+ <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
<application />
</manifest>
diff --git a/partner_support/g3doc/SeriesIdColumnForPartners.md b/partner_support/g3doc/SeriesIdColumnForPartners.md
new file mode 100644
index 0000000..cd44db0
--- /dev/null
+++ b/partner_support/g3doc/SeriesIdColumnForPartners.md
@@ -0,0 +1,30 @@
+# 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
new file mode 100644
index 0000000..0ba7cff
--- /dev/null
+++ b/partner_support/g3doc/TurnOffEmbeddedTuner.md
@@ -0,0 +1,15 @@
+# 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 f1edad3..804691a 100644
--- a/partner_support/sample_customization/AndroidManifest.xml
+++ b/partner_support/sample_customization/AndroidManifest.xml
@@ -18,13 +18,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tvcustomization">
<!-- Customization package must have this permission to customize TV apps. -->
- <uses-permission android:name="com.android.tv.permission.CUSTOMIZE_TV_APP"/>
+ <uses-permission android:name="com.google.android.tv.permission.CUSTOMIZE_TV_APP"/>
<!-- Enable leanback library support. -->
<uses-feature android:name="android.software.leanback" android:required="true" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
- <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="27" android:minSdkVersion="23"/>
<application android:label="Partner Customization"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
diff --git a/partner_support/sample_customization/res/mipmap-hdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-hdpi/ic_launcher.png
old mode 100755
new mode 100644
index 26add7f..454c515
--- a/partner_support/sample_customization/res/mipmap-hdpi/ic_launcher.png
+++ b/partner_support/sample_customization/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/mipmap-mdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-mdpi/ic_launcher.png
old mode 100755
new mode 100644
index 1ac20db..5e53eb5
--- a/partner_support/sample_customization/res/mipmap-mdpi/ic_launcher.png
+++ b/partner_support/sample_customization/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/mipmap-xhdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
index f6cf645..898bac4
--- a/partner_support/sample_customization/res/mipmap-xhdpi/ic_launcher.png
+++ b/partner_support/sample_customization/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/mipmap-xxhdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-xxhdpi/ic_launcher.png
old mode 100755
new mode 100644
index 72a250d..9da2990
--- a/partner_support/sample_customization/res/mipmap-xxhdpi/ic_launcher.png
+++ b/partner_support/sample_customization/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/mipmap-xxxhdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-xxxhdpi/ic_launcher.png
old mode 100755
new mode 100644
index 648001f..ff5c4b1
--- a/partner_support/sample_customization/res/mipmap-xxxhdpi/ic_launcher.png
+++ b/partner_support/sample_customization/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/values/bools.xml b/partner_support/sample_customization/res/values/bools.xml
index 54fbe07..259548b 100644
--- a/partner_support/sample_customization/res/values/bools.xml
+++ b/partner_support/sample_customization/res/values/bools.xml
@@ -17,4 +17,6 @@
<resources>
<bool name="tvprovider_allows_system_inserts_to_program_table">true</bool>
+ <bool name="tvprovider_allows_column_creation">true</bool>
+ <bool name="turn_off_embedded_tuner">true</bool>
</resources>
\ No newline at end of file
diff --git a/partner_support/samples/Android.mk b/partner_support/samples/Android.mk
index 922a5b5..2e771a5 100644
--- a/partner_support/samples/Android.mk
+++ b/partner_support/samples/Android.mk
@@ -16,7 +16,7 @@
android-support-core-ui \
android-support-v7-recyclerview \
android-support-v17-leanback \
- android-support-tv-provider
+ androidx.tvprovider_tvprovider
LOCAL_USE_AAPT2 := true
diff --git a/partner_support/samples/AndroidManifest.xml b/partner_support/samples/AndroidManifest.xml
index b9e086c..d91c603 100644
--- a/partner_support/samples/AndroidManifest.xml
+++ b/partner_support/samples/AndroidManifest.xml
@@ -29,12 +29,13 @@
<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="26" android:minSdkVersion="23"/>
+ <uses-sdk android:targetSdkVersion="27" 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"
+ tools:replace="android:label,icon,theme,appComponentFactory"
android:icon="@mipmap/ic_launcher"
- android:theme="@android:style/Theme.Holo.Light.NoActionBar" >
+ android:theme="@android:style/Theme.Holo.Light.NoActionBar"
+ android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
<activity android:name=".SampleTvInputSetupActivity"
android:theme="@style/Theme.Leanback.GuidedStep">
<intent-filter>
diff --git a/partner_support/samples/res/mipmap-hdpi/ic_launcher.png b/partner_support/samples/res/mipmap-hdpi/ic_launcher.png
old mode 100755
new mode 100644
index a827add..a044d2c
--- a/partner_support/samples/res/mipmap-hdpi/ic_launcher.png
+++ b/partner_support/samples/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/mipmap-mdpi/ic_launcher.png b/partner_support/samples/res/mipmap-mdpi/ic_launcher.png
old mode 100755
new mode 100644
index d7d36f2..26307c2
--- a/partner_support/samples/res/mipmap-mdpi/ic_launcher.png
+++ b/partner_support/samples/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/mipmap-xhdpi/ic_launcher.png b/partner_support/samples/res/mipmap-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
index 210bfcf..4964683
--- a/partner_support/samples/res/mipmap-xhdpi/ic_launcher.png
+++ b/partner_support/samples/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/mipmap-xxhdpi/ic_launcher.png b/partner_support/samples/res/mipmap-xxhdpi/ic_launcher.png
old mode 100755
new mode 100644
index 59a090c..93db554
--- a/partner_support/samples/res/mipmap-xxhdpi/ic_launcher.png
+++ b/partner_support/samples/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/mipmap-xxxhdpi/ic_launcher.png b/partner_support/samples/res/mipmap-xxxhdpi/ic_launcher.png
old mode 100755
new mode 100644
index 388b6eb..cfc2fb1
--- a/partner_support/samples/res/mipmap-xxxhdpi/ic_launcher.png
+++ b/partner_support/samples/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java
index 35f4b69..ec7589c 100644
--- a/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java
@@ -23,8 +23,6 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.support.annotation.NonNull;
-import android.support.media.tv.Channel;
-import android.support.media.tv.TvContractCompat;
import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
@@ -32,6 +30,8 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.tvprovider.media.tv.Channel;
+import androidx.tvprovider.media.tv.TvContractCompat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
diff --git a/partner_support/src/com/google/android/tv/partner/support/AutoValue_EpgInput.java b/partner_support/src/com/google/android/tv/partner/support/AutoValue_EpgInput.java
deleted file mode 100644
index aad51c7..0000000
--- a/partner_support/src/com/google/android/tv/partner/support/AutoValue_EpgInput.java
+++ /dev/null
@@ -1,97 +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.google.android.tv.partner.support;
-
-
-
-/**
- * Hand copy of generated Autovalue class.
- *
- * TODO get autovalue working
- */
-final class AutoValue_EpgInput extends EpgInput {
-
- private final long id;
- private final String inputId;
- private final String lineupId;
-
- AutoValue_EpgInput(
- long id,
- String inputId,
- String lineupId) {
- this.id = id;
- if (inputId == null) {
- throw new NullPointerException("Null inputId");
- }
- this.inputId = inputId;
- if (lineupId == null) {
- throw new NullPointerException("Null lineupId");
- }
- this.lineupId = lineupId;
- }
-
- @Override
- public long getId() {
- return id;
- }
-
- @Override
- public String getInputId() {
- return inputId;
- }
-
- @Override
- public String getLineupId() {
- return lineupId;
- }
-
- @Override
- public String toString() {
- return "EpgInput{"
- + "id=" + id + ", "
- + "inputId=" + inputId + ", "
- + "lineupId=" + lineupId
- + "}";
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o instanceof EpgInput) {
- EpgInput that = (EpgInput) o;
- return (this.id == that.getId())
- && (this.inputId.equals(that.getInputId()))
- && (this.lineupId.equals(that.getLineupId()));
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- int h$ = 1;
- h$ *= 1000003;
- h$ ^= (int) ((id >>> 32) ^ id);
- h$ *= 1000003;
- h$ ^= inputId.hashCode();
- h$ *= 1000003;
- h$ ^= lineupId.hashCode();
- return h$;
- }
-
-}
diff --git a/partner_support/src/com/google/android/tv/partner/support/AutoValue_Lineup.java b/partner_support/src/com/google/android/tv/partner/support/AutoValue_Lineup.java
deleted file mode 100644
index 076f8a2..0000000
--- a/partner_support/src/com/google/android/tv/partner/support/AutoValue_Lineup.java
+++ /dev/null
@@ -1,114 +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.google.android.tv.partner.support;
-
-import android.support.annotation.Nullable;
-import java.util.List;
-
-/**
- * Hand copy of generated Autovalue class.
- *
- * TODO get autovalue working
- */
-
-final class AutoValue_Lineup extends Lineup {
-
- private final String id;
- private final int type;
- private final String name;
- private final List<String> channels;
-
- AutoValue_Lineup(
- String id,
- int type,
- @Nullable String name,
- List<String> channels) {
- if (id == null) {
- throw new NullPointerException("Null id");
- }
- this.id = id;
- this.type = type;
- this.name = name;
- if (channels == null) {
- throw new NullPointerException("Null channels");
- }
- this.channels = channels;
- }
-
- @Override
- public String getId() {
- return id;
- }
-
- @Override
- public int getType() {
- return type;
- }
-
- @Nullable
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public List<String> getChannels() {
- return channels;
- }
-
- @Override
- public String toString() {
- return "Lineup{"
- + "id=" + id + ", "
- + "type=" + type + ", "
- + "name=" + name + ", "
- + "channels=" + channels
- + "}";
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o instanceof Lineup) {
- Lineup that = (Lineup) o;
- return (this.id.equals(that.getId()))
- && (this.type == that.getType())
- && ((this.name == null) ? (that.getName() == null) : this.name.equals(that.getName()))
- && (this.channels.equals(that.getChannels()));
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- int h$ = 1;
- h$ *= 1000003;
- h$ ^= id.hashCode();
- h$ *= 1000003;
- h$ ^= type;
- h$ *= 1000003;
- h$ ^= (name == null) ? 0 : name.hashCode();
- h$ *= 1000003;
- h$ ^= channels.hashCode();
- return h$;
- }
-
-}
diff --git a/partner_support/src/com/google/android/tv/partner/support/BaseCustomization.java b/partner_support/src/com/google/android/tv/partner/support/BaseCustomization.java
index e40d90d..1f7198e 100644
--- a/partner_support/src/com/google/android/tv/partner/support/BaseCustomization.java
+++ b/partner_support/src/com/google/android/tv/partner/support/BaseCustomization.java
@@ -83,7 +83,14 @@
? 0
: res.getIdentifier(resourceName, RES_TYPE_BOOLEAN, packageName);
if (DEBUG) {
- Log.d(TAG, "Boolean resource " + resourceName + " has " + resId);
+ Log.d(
+ TAG,
+ "Boolean resource "
+ + resourceName
+ + " has "
+ + resId
+ + " with value "
+ + (resId == 0 ? "missing" : res.getBoolean(resId)));
}
return resId == 0 ? Optional.empty() : Optional.of(res.getBoolean(resId));
} catch (PackageManager.NameNotFoundException e) {
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 82cc463..20b3542 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
@@ -17,13 +17,14 @@
package com.google.android.tv.partner.support;
import android.content.ContentValues;
+import com.google.auto.value.AutoValue;
/**
* Value class representing a TV Input that uses Live TV EPG.
*
* @see {@link EpgContract.EpgInputs}
*/
-// TODO(b/72052568): Get autovalue to work in aosp master
+@AutoValue
public abstract class EpgInput {
public static EpgInput createEpgChannel(long id, String inputId, String lineupId) {
diff --git a/partner_support/src/com/google/android/tv/partner/support/EpgInputs.java b/partner_support/src/com/google/android/tv/partner/support/EpgInputs.java
index 53485ec..dddcd08 100644
--- a/partner_support/src/com/google/android/tv/partner/support/EpgInputs.java
+++ b/partner_support/src/com/google/android/tv/partner/support/EpgInputs.java
@@ -59,6 +59,8 @@
result.add(EpgInput.createEpgChannel(contentValues));
}
return result;
+ } catch (Exception e) {
+ return Collections.emptySet();
}
}
diff --git a/partner_support/src/com/google/android/tv/partner/support/Lineup.java b/partner_support/src/com/google/android/tv/partner/support/Lineup.java
index 6123eeb..c5d3046 100644
--- a/partner_support/src/com/google/android/tv/partner/support/Lineup.java
+++ b/partner_support/src/com/google/android/tv/partner/support/Lineup.java
@@ -18,12 +18,13 @@
import android.content.ContentValues;
import android.support.annotation.Nullable;
+import com.google.auto.value.AutoValue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Value class for {@link com.google.android.tv.partner.support.EpgContract.Lineups} */
-// TODO(b/72052568): Get autovalue to work in aosp master
+@AutoValue
public abstract class Lineup {
/** Lineup type for cable. */
public static final int LINEUP_CABLE = 0;
diff --git a/partner_support/src/com/google/android/tv/partner/support/PartnerCustomizations.java b/partner_support/src/com/google/android/tv/partner/support/PartnerCustomizations.java
index 7ff168e..1133107 100644
--- a/partner_support/src/com/google/android/tv/partner/support/PartnerCustomizations.java
+++ b/partner_support/src/com/google/android/tv/partner/support/PartnerCustomizations.java
@@ -32,6 +32,11 @@
public static final String TVPROVIDER_ALLOWS_SYSTEM_INSERTS_TO_PROGRAM_TABLE =
"tvprovider_allows_system_inserts_to_program_table";
+ public static final String TVPROVIDER_ALLOWS_COLUMN_CREATION =
+ "tvprovider_allows_column_creation";
+
+ public static final String TURN_OFF_EMBEDDED_TUNER = "turn_off_embedded_tuner";
+
public PartnerCustomizations(Context context) {
super(context, CUSTOMIZE_PERMISSIONS);
}
@@ -40,4 +45,12 @@
return getBooleanResource(context, TVPROVIDER_ALLOWS_SYSTEM_INSERTS_TO_PROGRAM_TABLE)
.orElse(false);
}
+
+ public boolean doesTvProviderAllowColumnCreation(Context context) {
+ return getBooleanResource(context, TVPROVIDER_ALLOWS_COLUMN_CREATION).orElse(false);
+ }
+
+ public boolean turnOffEmbeddedTuner(Context context) {
+ return getBooleanResource(context, TURN_OFF_EMBEDDED_TUNER).orElse(false);
+ }
}
diff --git a/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/BaseCustomizationTest.java b/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/BaseCustomizationTest.java
index a6292e3..e217058 100644
--- a/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/BaseCustomizationTest.java
+++ b/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/BaseCustomizationTest.java
@@ -25,12 +25,12 @@
import android.content.pm.PackageManager;
import com.android.tv.testing.TestSingletonApp;
import com.android.tv.testing.constants.ConfigConstants;
-import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
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;
@@ -38,15 +38,8 @@
// TODO: move to partner-support
-@RunWith(GoogleRobolectricTestRunner.class)
-@Config(
- manifest =
- "//third_party/java_src/android_app/live_channels/common/src"
- + "/com/android/tv/common"
- + ":common/AndroidManifest.xml",
- sdk = ConfigConstants.SDK,
- application = TestSingletonApp.class
-)
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class)
public class BaseCustomizationTest {
private static final String[] PERMISSIONS = {"com.example.permission"};
diff --git a/proguard.flags b/proguard.flags
index 69b1786..b3795d6 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -68,3 +68,6 @@
# Grpc used by epg via reflection
-keep class io.grpc.internal.DnsNameResolverProvider
+
+# Don't warn about checkerframework in Android proguard
+-dontwarn org.checkerframework.**
diff --git a/res/drawable-xhdpi/bg_protection.png b/res/drawable-xhdpi/bg_protection.png
deleted file mode 100644
index 02df25a..0000000
--- a/res/drawable-xhdpi/bg_protection.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_store.png b/res/drawable-xhdpi/ic_app_store.png
similarity index 100%
rename from res/drawable-xhdpi/ic_store.png
rename to res/drawable-xhdpi/ic_app_store.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_check_circle_white_48dp.png b/res/drawable-xhdpi/ic_check_circle_white_48dp.png
deleted file mode 100644
index a1cf83e..0000000
--- a/res/drawable-xhdpi/ic_check_circle_white_48dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_developer_mode_tv_white_48dp.png b/res/drawable-xhdpi/ic_developer_mode_tv_white_48dp.png
deleted file mode 100644
index 594af85..0000000
--- a/res/drawable-xhdpi/ic_developer_mode_tv_white_48dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_error_outline_pink_24dp.png b/res/drawable-xhdpi/ic_error_outline_pink_24dp.png
new file mode 100644
index 0000000..d48bece
--- /dev/null
+++ b/res/drawable-xhdpi/ic_error_outline_pink_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_error_white_48dp.png b/res/drawable-xhdpi/ic_error_white_48dp.png
deleted file mode 100644
index 8c2cf1e..0000000
--- a/res/drawable-xhdpi/ic_error_white_48dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_live_channels.png b/res/drawable-xhdpi/ic_live_channels.png
deleted file mode 100644
index bb1c2d9..0000000
--- a/res/drawable-xhdpi/ic_live_channels.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tv_app.png b/res/drawable-xhdpi/ic_tv_app.png
new file mode 100644
index 0000000..c061bf0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tv_app.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_live_channels_96x96.png b/res/drawable-xhdpi/ic_tv_app_96x96.png
similarity index 100%
rename from res/drawable-xhdpi/ic_live_channels_96x96.png
rename to res/drawable-xhdpi/ic_tv_app_96x96.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_warning_white_18dp.png b/res/drawable-xhdpi/ic_warning_white_18dp.png
deleted file mode 100644
index 13d573e..0000000
--- a/res/drawable-xhdpi/ic_warning_white_18dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_warning_white_96dp.png b/res/drawable-xhdpi/ic_warning_white_96dp.png
deleted file mode 100644
index 50d1f29..0000000
--- a/res/drawable-xhdpi/ic_warning_white_96dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_warning_yellow_24dp.png b/res/drawable-xhdpi/ic_warning_yellow_24dp.png
new file mode 100644
index 0000000..accb061
--- /dev/null
+++ b/res/drawable-xhdpi/ic_warning_yellow_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/banner.png b/res/drawable-xhdpi/live_tv_banner.png
similarity index 100%
rename from res/drawable-xhdpi/banner.png
rename to res/drawable-xhdpi/live_tv_banner.png
Binary files differ
diff --git a/res/drawable-xhdpi/usb_antenna.png b/res/drawable-xhdpi/usb_antenna.png
deleted file mode 100644
index ca5b2d7..0000000
--- a/res/drawable-xhdpi/usb_antenna.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/menu_background.xml b/res/drawable/menu_background.xml
index bdd55c8..defee88 100644
--- a/res/drawable/menu_background.xml
+++ b/res/drawable/menu_background.xml
@@ -14,8 +14,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/bg_protection"
- android:tileModeX="repeat"
- android:tileModeY="mirror" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <!-- 0% to 20%black half way down then 100% black-->
+ <gradient
+ android:startColor="#00000000"
+ android:endColor="#000000"
+ android:centerColor="#a0000000"
+ android:centerY=".5"
+ android:angle="270" />
+</shape>
diff --git a/res/layout/channel_banner.xml b/res/layout/channel_banner.xml
index 3f105fe..4d3cc24 100644
--- a/res/layout/channel_banner.xml
+++ b/res/layout/channel_banner.xml
@@ -83,13 +83,22 @@
android:layout_toEndOf="@id/anchor"
android:visibility="gone" />
+ <ImageView android:id="@+id/channel_signal_strength"
+ android:layout_width="@dimen/channel_banner_input_logo_size"
+ android:layout_height="@dimen/channel_banner_input_logo_size"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="-2dp"
+ android:layout_alignBottom="@id/anchor"
+ android:layout_toEndOf="@id/tvinput_logo"
+ android:visibility="gone" />
+
<TextView android:id="@+id/channel_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginBottom="-4sp"
android:layout_alignBottom="@id/anchor"
- android:layout_toEndOf="@id/tvinput_logo"
+ android:layout_toEndOf="@id/channel_signal_strength"
android:singleLine="true"
android:ellipsize="end"
android:maxWidth="@dimen/channel_name_max_width"
diff --git a/res/layout/dvr_details_description.xml b/res/layout/dvr_details_description.xml
index d55688b..ee74952 100644
--- a/res/layout/dvr_details_description.xml
+++ b/res/layout/dvr_details_description.xml
@@ -42,6 +42,27 @@
android:orientation="vertical"
android:background="?android:attr/selectableItemBackground">
+ <LinearLayout android:id="@+id/dvr_details_description_error_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone">
+
+ <ImageView android:layout_width="30dp"
+ android:layout_height="27dp"
+ android:paddingTop="9dp"
+ android:paddingLeft="12dp"
+ android:src="@drawable/ic_error_outline_pink_24dp"/>
+
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="9dp"
+ android:paddingRight="12dp"
+ android:textColor="@color/dvr_recording_failed_text_color"
+ android:text="@string/dvr_recording_failed"
+ style="?attr/detailsDescriptionBodyStyle" />
+ </LinearLayout>
+
<TextView android:id="@+id/dvr_details_description_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/res/layout/dvr_recording_card_view.xml b/res/layout/dvr_recording_card_view.xml
index 3e95351..3bf9bf6 100644
--- a/res/layout/dvr_recording_card_view.xml
+++ b/res/layout/dvr_recording_card_view.xml
@@ -30,7 +30,7 @@
android:layout_gravity="center_horizontal"
android:scaleType="centerCrop"
android:contentDescription="@null"
- tv:layout_viewType="main" />
+ tv:layout_viewType="main"/>
<ProgressBar android:id="@+id/recording_progress"
style="@android:style/Widget.ProgressBar.Horizontal"
@@ -42,21 +42,7 @@
android:indeterminate="false"
android:visibility="gone"
android:max="100"
- android:layout_gravity="bottom" />
-
- <FrameLayout android:id="@+id/affiliated_icon_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/card_image_gradient"
- android:visibility="invisible">
-
- <ImageView android:id="@+id/affiliated_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|right"
- android:layout_margin="12dp" />
-
- </FrameLayout>
+ android:layout_gravity="bottom"/>
</FrameLayout>
<LinearLayout android:id="@+id/info_area"
@@ -99,10 +85,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" >
+ <ImageView android:id="@+id/content_icon"
+ android:paddingTop="2dp"
+ android:layout_width="13dp"
+ android:layout_height="15dp"
+ android:gravity="start"
+ android:visibility="gone"/>
+
<TextView android:id="@+id/content_major"
+ android:layout_toEndOf="@+id/content_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="start"
style="@style/dvr_card_view_content_text" />
<TextView android:id="@+id/content_minor"
diff --git a/res/layout/dvr_schedules_item.xml b/res/layout/dvr_schedules_item.xml
index 90e1123..9e9ee6a 100644
--- a/res/layout/dvr_schedules_item.xml
+++ b/res/layout/dvr_schedules_item.xml
@@ -100,14 +100,25 @@
android:lines="1"
android:textColor="@color/dvr_schedules_item_info"/>
</LinearLayout>
- <TextView android:id="@+id/conflict_info"
- android:layout_width="match_parent"
+
+ <LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="start"
- android:textSize="10sp"
- android:layout_marginBottom="@dimen/dvr_schedules_item_conflict_info_bottom_margin"
- android:textColor="@color/dvr_schedules_item_info"
- android:visibility="gone"/>
+ android:orientation="horizontal">
+ <ImageView android:id="@+id/extra_info_icon"
+ android:layout_width="13dp"
+ android:layout_height="13dp"
+ android:paddingTop="2dp"
+ android:src="@drawable/ic_error_outline_pink_24dp"
+ android:visibility="gone"/>
+ <TextView android:id="@+id/extra_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:textSize="10sp"
+ android:layout_marginBottom="@dimen/dvr_schedules_item_conflict_info_bottom_margin"
+ android:textColor="@color/dvr_schedules_item_info"
+ android:visibility="gone"/>
+ </LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/res/layout/menu_card_down.xml b/res/layout/menu_card_down.xml
new file mode 100644
index 0000000..0ccfc89
--- /dev/null
+++ b/res/layout/menu_card_down.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<com.android.tv.menu.SimpleCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/card_layout_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:elevation="@dimen/card_elevation_normal"
+ android:focusable="true"
+ android:clickable="true">
+
+ <ImageView
+ android:layout_width="@dimen/card_image_layout_width"
+ android:layout_height="@dimen/card_image_layout_height"
+ android:background="@color/channel_card_guide"
+ android:paddingBottom="16dp"
+ android:paddingEnd="30dp"
+ android:paddingStart="30dp"
+ android:paddingTop="16dp"
+ android:src="@drawable/quantum_ic_arrow_downward_white_36" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/card_meta_layout_height"
+ android:paddingStart="@dimen/card_meta_padding_start"
+ android:paddingEnd="@dimen/card_meta_padding_end"
+ android:paddingTop="@dimen/card_meta_padding_top"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/card_meta_text_color"
+ android:background="@color/guide_card_meta_background"
+ android:text="@string/channels_item_down"
+ android:textSize="12sp" />
+
+</com.android.tv.menu.SimpleCardView>
diff --git a/res/layout/menu_card_up.xml b/res/layout/menu_card_up.xml
new file mode 100644
index 0000000..2ba365e
--- /dev/null
+++ b/res/layout/menu_card_up.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<com.android.tv.menu.SimpleCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/card_layout_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:elevation="@dimen/card_elevation_normal"
+ android:focusable="true"
+ android:clickable="true">
+
+ <ImageView
+ android:layout_width="@dimen/card_image_layout_width"
+ android:layout_height="@dimen/card_image_layout_height"
+ android:background="@color/channel_card_guide"
+ android:paddingBottom="16dp"
+ android:paddingEnd="30dp"
+ android:paddingStart="30dp"
+ android:paddingTop="16dp"
+ android:src="@drawable/quantum_ic_arrow_upward_white_36" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/card_meta_layout_height"
+ android:paddingStart="@dimen/card_meta_padding_start"
+ android:paddingEnd="@dimen/card_meta_padding_end"
+ android:paddingTop="@dimen/card_meta_padding_top"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/card_meta_text_color"
+ android:background="@color/guide_card_meta_background"
+ android:text="@string/channels_item_up"
+ android:textSize="12sp" />
+
+</com.android.tv.menu.SimpleCardView>
diff --git a/res/layout/pin_dialog.xml b/res/layout/pin_dialog.xml
index 5071717..d40d70e 100644
--- a/res/layout/pin_dialog.xml
+++ b/res/layout/pin_dialog.xml
@@ -35,7 +35,8 @@
android:textColor="@color/pin_dialog_text_color"
android:fontFamily="@string/font"
android:visibility="invisible"
- android:singleLine="false"/>
+ android:singleLine="false"
+ android:focusableInTouchMode="true"/>
<LinearLayout
android:id="@+id/enter_pin"
@@ -54,36 +55,14 @@
android:fontFamily="@string/font"
android:singleLine="false" />
- <LinearLayout
+ <com.android.tv.dialog.picker.PinPicker
+ android:id="@+id/pin_picker"
+ android:importantForAccessibility="yes"
android:layout_width="match_parent"
- android:layout_height="144dp"
+ android:layout_height="154dp"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:gravity="center"
- android:orientation="horizontal">
-
- <view class="com.android.tv.dialog.PinDialogFragment$PinNumberPicker"
- android:id="@+id/first"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <view class="com.android.tv.dialog.PinDialogFragment$PinNumberPicker"
- android:id="@+id/second"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp" />
-
- <view class="com.android.tv.dialog.PinDialogFragment$PinNumberPicker"
- android:id="@+id/third"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp" />
-
- <view class="com.android.tv.dialog.PinDialogFragment$PinNumberPicker"
- android:id="@+id/fourth"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp" />
- </LinearLayout>
+ />
</LinearLayout>
</FrameLayout>
diff --git a/res/layout/pin_number_picker.xml b/res/layout/pin_number_picker.xml
deleted file mode 100644
index 8e8de9f..0000000
--- a/res/layout/pin_number_picker.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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.
- -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="48dp"
- android:layout_height="144dp">
-
- <TextView android:id="@+id/focused_background"
- android:layout_width="@dimen/pin_number_picker_text_view_width"
- android:layout_height="@dimen/pin_number_picker_text_view_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:textSize="@dimen/pin_number_picker_text_size"
- android:textColor="@color/pin_number_picker_text_color"
- android:fontFamily="@string/light_font"
- android:background="@drawable/pin_number_picker_focused_background" />
-
- <LinearLayout
- android:id="@+id/number_view_holder"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:focusable="true"
- android:orientation="vertical">
-
- <TextView android:id="@+id/previous2_number"
- style="@style/pin_number_view"/>
- <TextView android:id="@+id/previous_number"
- style="@style/pin_number_view"/>
- <TextView android:id="@+id/current_number"
- style="@style/pin_number_view"/>
- <TextView android:id="@+id/next_number"
- style="@style/pin_number_view"/>
- <TextView android:id="@+id/next2_number"
- style="@style/pin_number_view"/>
- </LinearLayout>
-
-</FrameLayout>
diff --git a/res/layout/tunable_tv_view.xml b/res/layout/tunable_tv_view.xml
index 549d053..00c9908 100644
--- a/res/layout/tunable_tv_view.xml
+++ b/res/layout/tunable_tv_view.xml
@@ -17,27 +17,6 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
- <View android:id="@+id/channel_up"
- android:layout_width="wrap_content"
- android:focusable="false"
- android:focusableInTouchMode="true"
- android:layout_height="1dp"
- android:layout_gravity="top" />
- <View android:id="@+id/placeholder"
- android:layout_width="1dp"
- android:layout_height="1dp"
- android:focusable="false"
- android:focusableInTouchMode="true"
- android:focusedByDefault="true"
- android:layout_gravity="center" />
-
- <View android:id="@+id/channel_down"
- android:layout_width="wrap_content"
- android:focusable="false"
- android:focusableInTouchMode="true"
- android:layout_height="1dp"
- android:layout_gravity="bottom" />
-
<com.android.tv.ui.AppLayerTvView android:id="@+id/tv_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index e57b054..0604dd2 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -29,31 +29,130 @@
<!-- The category strings to be displayed in the channel guide.
This list should be synced the data in src/com/android/tv/data/GenreItems.java -->
<eat-comment />
- <!-- Genre list [CHAR LIMIT=20] -->
+ <!-- Genre list [CHAR LIMIT=25] -->
<string-array name="genre_labels" translatable="true">
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "All channels", implies all channels will be shown in the program guide.
+ [CHAR LIMIT=25] -->
<item>All channels</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Family/Kids", implies only channels with "Family/Kids" programs will be shown in the
+ guide.
+ "Family/Kids" programs, are programs designed for families and safe for viewing by
+ young children.
+ [CHAR LIMIT=25] -->
<item>Family/Kids</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Sports", implies only channels with "Sports" programs will be shown in the guide.
+ "Sports" programs, include sporting events, news and other shows about sports.
+ [CHAR LIMIT=25] -->
<item>Sports</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Shopping", implies only channels with "Shopping" programs will be shown in the guide.
+ "Shopping" programs are TV shows where people can buy or bid on items.
+ [CHAR LIMIT=25] -->
<item>Shopping</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Movies", implies only channels with "Movies" programs will be shown in the guide.
+ [CHAR LIMIT=25] -->
<item>Movies</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Comedy", implies only channels with "Comedy" programs will be shown in the guide.
+ "Comedy" programs are generally intended to be humorous or amusing.
+ [CHAR LIMIT=25] -->
<item>Comedy</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Travel", implies only channels with "Travel" programs will be shown in the guide.
+ "Travel" programs feature popular destination or travel reviews.
+ [CHAR LIMIT=25] -->
<item>Travel</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Drama", implies only channels with "Drama" programs will be shown in the guide.
+ "Drama" programs are fictional shows, featuring actors.
+ [CHAR LIMIT=25] -->
<item>Drama</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Education", implies only channels with "Education" programs will be shown in the guide.
+ "Education" programs are designed to teach, either formally or informally.
+ [CHAR LIMIT=25] -->
<item>Education</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Animal/Wildlife", implies only channels with "Animal/Wildlife" programs will be shown in
+ the guide.
+ "Animal/Wildlife" programs are about wild animals or pets.
+ [CHAR LIMIT=25] -->
<item>Animal/Wildlife</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "News", implies only channels with "News" programs will be shown in the guide.
+ "News" programs report world or local events as they unfold.
+ [CHAR LIMIT=25] -->
<item>News</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Gaming", implies only channels with "Gaming" programs will be shown in the guide.
+ "Gaming" programs rare about games, including video games and board games,
+ but excluding sports.
+ [CHAR LIMIT=25] -->
<item>Gaming</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Arts", implies only channels with "Arts" programs will be shown in the guide.
+ "Arts" programs about artistic endeavours or events like dance, music theater, drawing.
+ [CHAR LIMIT=25] -->
<item>Arts</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Entertainment", implies only channels with "Entertainment" programs will be shown in
+ the guide.
+ "Entertainment" programs discuss news and the people involved in the entertainment
+ industry.
+ [CHAR LIMIT=25] -->
<item>Entertainment</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Lifestyle", implies only channels with "Lifestyle" programs will be shown in
+ the guide.
+ "Lifestyle" programs feature topics such as fashion, diet, exercise, health and leisure
+ pursuits.
+ [CHAR LIMIT=25] -->
<item>Lifestyle</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Music", implies only channels with "Music" programs will be shown in
+ the guide.
+ "Music" programs feature live or recorded music.
+ [CHAR LIMIT=25] -->
<item>Music</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Premier", implies only channels with "Premier" programs will be shown in
+ the guide.
+ "Premier" programs are available at an extra cost.
+ [CHAR LIMIT=25] -->
<item>Premier</item>
+ <!-- This is an item in a list to filter channels shown in a TV program guide, based on
+ genres.
+ "Tech/Science", implies only channels with "Tech/Science" programs will be shown in
+ the guide.
+ "Tech/Science" programs are about science or technology .
+ [CHAR LIMIT=25] -->
<item>Tech/Science</item>
</string-array>
<!-- Titles in the onboarding page. -->
<string-array name="welcome_page_titles">
- <item>Live TV</item>
+ <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>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index e0a0b99..b68feb1 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -158,4 +158,6 @@
<color name="dvr_guided_step_action_text_color_selected">#111111</color>
<color name="dvr_detail_default_background">#FF01579B</color>
<color name="dvr_detail_default_background_scrim">#CC000000</color>
+ <color name="dvr_recording_failed_text_color">#FFCDD2</color>
+ <color name="dvr_recording_conflict_text_color">#FFE082</color>
</resources>
diff --git a/tuner/tests/TestManifest.xml b/res/values/strings-custom.xml
similarity index 68%
rename from tuner/tests/TestManifest.xml
rename to res/values/strings-custom.xml
index f84aa90..22f7331 100644
--- a/tuner/tests/TestManifest.xml
+++ b/res/values/strings-custom.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2018 The Android Open Source Project
+ ~ 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.
@@ -14,10 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+<resources>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tv.tuner.tests">
- <!-- android_local_test needs minSdkVersion set -->
- <uses-sdk android:minSdkVersion="23"/>
+ <!-- Name of application [CHAR LIMIT=NONE] -->
+ <string name="app_name" translatable="false">Live TV</string>
-</manifest>
\ No newline at end of file
+</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cea4ee6..3682475 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -26,21 +26,18 @@
<string name="option_item_divider_font" translatable="false">@string/condensed_font</string>
- <!-- Name of application [CHAR LIMIT=NONE] -->
- <string name="app_name">Live TV</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_receiveInputEvent" translatable="false">receive input events from Live TV 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 Live TV 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 Live TV 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 Live TV 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 />
@@ -90,6 +87,12 @@
<!-- Label of Program guide item in the channel list row. [CHAR LIMIT=23] -->
<string name="channels_item_program_guide">Program guide</string>
+ <!-- Label of the item in the "channel menu" that changes the current TV channel in the "up"
+ direction, to the next larger channel number. [CHAR LIMIT=23] -->
+ <string name="channels_item_up">Channel up</string>
+ <!-- Label of the item in the "channel menu" that changes the current TV channel in the "down"
+ direction, to the next smaller channel number. [CHAR LIMIT=23] -->
+ <string name="channels_item_down">Channel down</string>
<!-- Label of setup item in the channel list row. The item is shown only
when new inputs are installed. [CHAR LIMIT=23] -->
<string name="channels_item_setup">New channels available</string>
@@ -366,10 +369,22 @@
<eat-comment />
<!-- Description on the locked screen when current channel is locked by parental control. [CHAR LIMIT=NONE] -->
<string name="tvview_channel_locked">To watch this channel, press Right and enter your PIN</string>
+ <!-- Description on the locked screen when current channel is locked by parental control and talk back is turned on.
+ "press select" refers to a button on the remote. [CHAR LIMIT=NONE] -->
+ <string name="tvview_channel_locked_talkback">To watch this channel, press select and enter your PIN</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">To watch this program, press Right and enter your PIN</string>
+ <!-- Description on the locked screen when the rating of the current content is restricted by parental control and talk back is turned on.
+ "press select" refers to a button on the remote.[CHAR LIMIT=NONE] -->
+ <string name="tvview_content_locked_talkback">To watch this program, press select and enter your PIN</string>
<!-- Description on the locked screen when the current content is unrated and it's restricted by parental control. [CHAR LIMIT=NONE] -->
<string name="tvview_content_locked_unrated">This program is unrated.\nTo watch this program, press Right and enter your PIN</string>
+ <!-- Description on the locked screen when the current content is unrated and it's restricted by parental control and talk back is turned on.
+ "press select" refers to a button on the remote.[CHAR LIMIT=NONE] -->
+ <string name="tvview_content_locked_unrated_talkback">This program is unrated.\nTo watch this program, press select and enter your PIN</string>
+ <!-- Description on the locked screen with the rating when the rating of the current content is restricted by parental control and talk back is turned on. [CHAR LIMIT=NONE]
+ "press select" refers to a button on the remote.-->
+ <string name="tvview_content_locked_format_talkback">This program is rated <xliff:g id="rating" example="TV_MA">%1$s</xliff:g>.\nTo watch this program, press select and enter your PIN.</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">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] -->
@@ -464,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 Live TV 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,
@@ -475,11 +490,13 @@
<string name="msg_all_channels_hidden">All source channels are hidden.\nSelect at least one channel to watch.</string>
<!-- Message displayed when availability is changed by unknown reason. [CHAR LIMIT=NONE] -->
<string name="msg_channel_unavailable_unknown">The video is unexpectedly unavailable</string>
+ <!-- Message displayed when a TV input (eg HDMI Cable) is not physically connected. [CHAR LIMIT=NONE] -->
+ <string name="msg_channel_unavailable_not_connected">No Signal. Please check your source connection.</string>
<!-- Message to notify the different use of Back Button: Home Button(To exit) Back button
(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">Live TV 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 />
@@ -505,13 +522,13 @@
<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">Live TV DVR</string>
+ <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">Live TV DVR</string>
+ <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">Live TV are recording.</string>
+ <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">Live TV are updating recording schedules.</string>
+ <string name="dvr_notification_content_text_loading" translatable="false"><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>
@@ -577,7 +594,11 @@
<!-- Description of a card view to show full list of scheduled recordings. [CHAR LIMIT=25] -->
<string name="dvr_full_schedule_card_view_title">Full schedule</string>
<!-- Description of failed recordings. [CHAR LIMIT=25] -->
- <string name="dvr_recording_failed">Recording Failed</string>
+ <string name="dvr_recording_failed">Recording Failed.</string>
+ <!-- Description of failed recordings. [CHAR LIMIT=25] -->
+ <string name="dvr_recording_failed_no_period">Recording Failed</string>
+ <!-- Description of recording conflicts. [CHAR LIMIT=25] -->
+ <string name="dvr_recording_conflict">Recording Conflict</string>
<!-- Description of how many following days the schedule list will show. [CHAR LIMIT=25] -->
<plurals name="dvr_full_schedule_card_view_content">
<item quantity="one">Next %1$d day</item>
@@ -607,6 +628,8 @@
<!-- DVR detailed page -->
<eat-comment />
+ <!-- Button label to schedule a recording. -->
+ <string name="dvr_detail_schedule_recording">Schedule recording</string>
<!-- Button label to cancel the recording schedule. -->
<string name="dvr_detail_cancel_recording">Cancel recording</string>
<!-- Button label to stop the current recording. -->
@@ -631,6 +654,18 @@
<string name="dvr_detail_view_schedule">View schedule</string>
<!-- Text label to indicate there's more text in the details description [CHAR LIMIT=20] -->
<string name="dvr_detail_read_more">Read more</string>
+ <!-- Description of failed recordings caused by system failures. -->
+ <string name="dvr_recording_failed_system_failure">System failure. (Error code: <xliff:g id="errorCode" example="0">%1$d</xliff:g>)</string>
+ <!-- Description of failed recordings when they are not started correctly. -->
+ <string name="dvr_recording_failed_not_started">Recording was not started. Please check the antenna and hard drive connections (if any).</string>
+ <!-- Description of failed recordings when required resource is busy. -->
+ <string name="dvr_recording_failed_resource_busy">Failed to tune to the channel. Possible causes: weak signal, poor antenna connection, or recording conflict.</string>
+ <!-- Description of failed recordings when the input is unavailable. -->
+ <string name="dvr_recording_failed_input_unavailable"><xliff:g id="inputId" example="com.example.partnersupportsampletvinput/.SampleTvInputService">%1$s</xliff:g> unavailable.</string>
+ <!-- Description of failed recordings when the input doesn't support recording. -->
+ <string name="dvr_recording_failed_input_dvr_unsupported">Recording of this channel is not supported.</string>
+ <!-- Description of failed recordings when the space is insufficient. -->
+ <string name="dvr_recording_failed_insufficient_space">Insufficient space. Please connect an external storage device or delete some existing recordings.</string>
<!-- DVR series settings -->
@@ -754,7 +789,7 @@
sufficient space.-->
<string name="dvr_error_insufficient_space_description_three_or_more_recordings">The recordings of <xliff:g id="programName_1" example="Friends">%1$s</xliff:g>, <xliff:g id="programName_2" example="Friends">%2$s</xliff:g> and <xliff:g id="programName_3" example="Friends">%3$s</xliff:g> didn\'t complete due to insufficient storage.</string>
<!-- Dialog title which will be shown when the current storage is too small for DVR. -->
- <string name="dvr_error_small_sized_storage_title">More stroage needed</string>
+ <string name="dvr_error_small_sized_storage_title">More storage needed</string>
<!-- Dialog description which will be shown when the current storage is too small for DVR. -->
<string name="dvr_error_small_sized_storage_description">You will be able to record programs. However there is not enough storage on your device to start recording. Please connect an external drive that is <xliff:g id="storage_size" example="10GB">%1$d</xliff:g>GB or larger and follow the steps to format it as device storage.</string>
<!-- Dialog title which will be shown when there is no free space on the current storage for DVR. -->
@@ -930,6 +965,20 @@
<item quantity="other">(%1$d minutes)</item>
</plurals>
+ <!-- DVR history list strings -->
+ <!-- Short description of failed recordings. -->
+ <string name="dvr_recording_failed_short">Failed.</string>
+ <!-- Short description of failed recordings when they are not started correctly. -->
+ <string name="dvr_recording_failed_not_started_short">Failed to start recording.</string>
+ <!-- Short description of failed recordings when required resource is busy. -->
+ <string name="dvr_recording_failed_resource_busy_short">Failed to tune to the channel.</string>
+ <!-- Short description of failed recordings when the input is unavailable. -->
+ <string name="dvr_recording_failed_input_unavailable_short"><xliff:g id="inputId" example="com.example.partnersupportsampletvinput/.SampleTvInputService">%1$s</xliff:g> unavailable.</string>
+ <!-- Short description of failed recordings when the input doesn't support recording. -->
+ <string name="dvr_recording_failed_input_dvr_unsupported_short">Recording not supported.</string>
+ <!-- Short description of failed recordings when the space is insufficient. -->
+ <string name="dvr_recording_failed_insufficient_space_short">Insufficient space.</string>
+
<!-- DVR date related strings -->
<eat-comment/>
<!-- Date text to represent today. -->
@@ -952,4 +1001,21 @@
<eat-comment/>
<!-- Name for recorded programs preview channel -->
<string name="recorded_programs_preview_channel">Recorded Programs</string>
+
+ <!-- Request Permission -->
+ <eat-comment />
+ <!-- Title of the dialog to show storage permission rationale -->
+ <string name="write_storage_permission_rationale_title">Request permission</string>
+ <!-- The users has asked to delete TV programs they have recorded, however the application
+ first needs a system permission to delete files.
+ This is the text of a dialog that explains to the users they will be asked to grant
+ Live TV permission to delete files.
+ "Allow Live TV to access photos, media, and files on your device?\" is from the text
+ from the system message at
+ https://tc.corp.google.com/btviewer/messagedetail?project=AndroidPlatform&msgId=7885942926944299560
+ -->
+ <string name="write_storage_permission_rationale_description">You will be asked to, \"Allow <xliff:g id="app_name">Live TV</xliff:g> to access photos, media, and files on your device?\"\n
+ Selecting \"Allow\", enables <xliff:g id="app_name">Live TV</xliff:g> to immediately
+ free storage space when deleting recorded TV programs.
+ This makes more space available for new recordings.</string>
</resources>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 2165a75..9653707 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -77,7 +77,6 @@
<item name="guidanceIconStyle">@style/TV.Dvr.GuidanceIconStyle</item>
<item name="guidedActionsListStyle">@style/TV.Dvr.GuidedActionsListStyle</item>
<item name="guidedActionItemContainerStyle">@style/TV.Dvr.GuidedActionItemContainerStyle</item>
- <item name="guidedActionContentWidthWeight">@string/lb_guidedactions_width_weight</item>
</style>
<style name="Theme.TV.Dvr.GuidedStep.Twoline.Action" parent = "Theme.TV.Dvr.GuidedStep">
@@ -120,4 +119,4 @@
clicking DVR cards overlapping with fragment transition. -->
<item name="android:windowAllowEnterTransitionOverlap">false</item>
</style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/src/com/android/tv/util/Filter.java b/settings.gradle
similarity index 67%
rename from src/com/android/tv/util/Filter.java
rename to settings.gradle
index 3e24a49..6d5cb54 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/settings.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.tv.util;
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
-}
+/*
+ * Experimental gradle configuration. This file may not be up to date.
+ */
+
+include ':common'
+include ':tuner'
+include ':SampleDvbTuner'
+project(":SampleDvbTuner").projectDir = file("tuner/SampleDvbTuner")
diff --git a/src/com/android/tv/AudioManagerHelper.java b/src/com/android/tv/AudioManagerHelper.java
deleted file mode 100644
index 942d431..0000000
--- a/src/com/android/tv/AudioManagerHelper.java
+++ /dev/null
@@ -1,133 +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;
-
-import android.app.Activity;
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Build;
-import com.android.tv.receiver.AudioCapabilitiesReceiver;
-import com.android.tv.ui.TunableTvView;
-import com.android.tv.ui.TunableTvViewPlayingApi;
-
-/** A helper class to help {@link MainActivity} to handle audio-related stuffs. */
-class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener {
- private static final float AUDIO_MAX_VOLUME = 1.0f;
- private static final float AUDIO_MIN_VOLUME = 0.0f;
- private static final float AUDIO_DUCKING_VOLUME = 0.3f;
-
- private final Activity mActivity;
- private final TunableTvViewPlayingApi mTvView;
- private final AudioManager mAudioManager;
- private final AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
-
- private boolean mAc3PassthroughSupported;
- private int mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
-
- AudioManagerHelper(Activity activity, TunableTvViewPlayingApi tvView) {
- mActivity = activity;
- mTvView = tvView;
- mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
- mAudioCapabilitiesReceiver =
- new AudioCapabilitiesReceiver(
- activity,
- new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() {
- @Override
- public void onAc3PassthroughCapabilityChange(boolean capability) {
- mAc3PassthroughSupported = capability;
- }
- });
- mAudioCapabilitiesReceiver.register();
- }
-
- /**
- * Sets suitable volume to {@link TunableTvView} according to the current audio focus. If the
- * focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is under PIP mode, this
- * method will finish the activity.
- */
- void setVolumeByAudioFocusStatus() {
- if (mTvView.isPlaying()) {
- switch (mAudioFocusStatus) {
- case AudioManager.AUDIOFOCUS_GAIN:
- if (mTvView.isTimeShiftAvailable()) {
- mTvView.timeshiftPlay();
- } else {
- mTvView.setStreamVolume(AUDIO_MAX_VOLUME);
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS:
- if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(mActivity)
- && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
- && mActivity.isInPictureInPictureMode()) {
- mActivity.finish();
- break;
- }
- // fall through
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- if (mTvView.isTimeShiftAvailable()) {
- mTvView.timeshiftPause();
- } else {
- mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- if (mTvView.isTimeShiftAvailable()) {
- mTvView.timeshiftPause();
- } else {
- mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
- }
- break;
- }
- }
- }
-
- /**
- * Tries to request audio focus from {@link AudioManager} and set volume according to the
- * returned result.
- */
- void requestAudioFocus() {
- int result =
- mAudioManager.requestAudioFocus(
- this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
- mAudioFocusStatus =
- (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
- ? AudioManager.AUDIOFOCUS_GAIN
- : AudioManager.AUDIOFOCUS_LOSS;
- setVolumeByAudioFocusStatus();
- }
-
- /** Abandons audio focus. */
- void abandonAudioFocus() {
- mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
- mAudioManager.abandonAudioFocus(this);
- }
-
- /** Returns {@code true} if the device supports AC3 pass-through. */
- boolean isAc3PassthroughSupported() {
- return mAc3PassthroughSupported;
- }
-
- /** Release the resources the helper class may occupied. */
- void release() {
- mAudioCapabilitiesReceiver.unregister();
- }
-
- @Override
- public void onAudioFocusChange(int focusChange) {
- mAudioFocusStatus = focusChange;
- setVolumeByAudioFocusStatus();
- }
-}
diff --git a/src/com/android/tv/util/Filter.java b/src/com/android/tv/ChannelChanger.java
similarity index 67%
copy from src/com/android/tv/util/Filter.java
copy to src/com/android/tv/ChannelChanger.java
index 3e24a49..5503569 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/src/com/android/tv/ChannelChanger.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.tv;
-package com.android.tv.util;
+/** Changes the channel. */
+public interface ChannelChanger {
-/** Interface to decide whether an input is filtered out or not. */
-public interface Filter<T> {
- /** Returns true, if {@code input} is acceptable. */
- boolean filter(T input);
+ void channelUp();
+
+ void channelDown();
}
diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java
index 8ab145a..fe13898 100644
--- a/src/com/android/tv/ChannelTuner.java
+++ b/src/com/android/tv/ChannelTuner.java
@@ -97,13 +97,7 @@
mStarted = true;
mChannelDataManager.addListener(mChannelDataManagerListener);
if (mChannelDataManager.isDbLoadFinished()) {
- mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- mChannelDataManagerListener.onLoadFinished();
- }
- });
+ mHandler.post(mChannelDataManagerListener::onLoadFinished);
}
}
diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java
index 4f298ed..ea17751 100644
--- a/src/com/android/tv/InputSessionManager.java
+++ b/src/com/android/tv/InputSessionManager.java
@@ -20,11 +20,8 @@
import android.content.Context;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
-import android.media.tv.TvRecordingClient;
-import android.media.tv.TvRecordingClient.RecordingCallback;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
-import android.media.tv.TvView.TvInputCallback;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -36,9 +33,15 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import com.android.tv.common.compat.TvRecordingClientCompat;
+import com.android.tv.common.compat.TvRecordingClientCompat.RecordingCallbackCompat;
+import com.android.tv.common.compat.TvViewCompat;
+import com.android.tv.common.compat.TvViewCompat.TvInputCallbackCompat;
import com.android.tv.data.api.Channel;
+import com.android.tv.dvr.DvrTvView;
import com.android.tv.ui.TunableTvView;
import com.android.tv.ui.TunableTvView.OnTuneListener;
+import com.android.tv.ui.api.TunableTvViewPlayingApi;
import com.android.tv.util.TvInputManagerHelper;
import java.util.Collections;
import java.util.List;
@@ -87,7 +90,9 @@
@MainThread
@NonNull
public TvViewSession createTvViewSession(
- TvView tvView, TunableTvView tunableTvView, TvInputCallback callback) {
+ TvViewCompat tvView,
+ TunableTvViewPlayingApi tunableTvView,
+ TvInputCallbackCompat callback) {
TvViewSession session = new TvViewSession(tvView, tunableTvView, callback);
mTvViewSessions.add(session);
if (DEBUG) Log.d(TAG, "TvView session created: " + session);
@@ -107,7 +112,7 @@
public RecordingSession createRecordingSession(
String inputId,
String tag,
- RecordingCallback callback,
+ RecordingCallbackCompat callback,
Handler handler,
long endTimeMs) {
RecordingSession session = new RecordingSession(inputId, tag, callback, handler, endTimeMs);
@@ -237,9 +242,10 @@
*/
@MainThread
public class TvViewSession {
- private final TvView mTvView;
- private final TunableTvView mTunableTvView;
- private final TvInputCallback mCallback;
+ private final TvViewCompat mTvView;
+ private final TunableTvViewPlayingApi mTunableTvView;
+ private final TvInputCallbackCompat mCallback;
+ private final boolean mIsDvrSession;
private Channel mChannel;
private String mInputId;
private Uri mChannelUri;
@@ -248,10 +254,14 @@
private boolean mTuned;
private boolean mNeedToBeRetuned;
- TvViewSession(TvView tvView, TunableTvView tunableTvView, TvInputCallback callback) {
+ TvViewSession(
+ TvViewCompat tvView,
+ TunableTvViewPlayingApi tunableTvView,
+ TvInputCallbackCompat callback) {
mTvView = tvView;
mTunableTvView = tunableTvView;
mCallback = callback;
+ mIsDvrSession = tunableTvView instanceof DvrTvView;
mTvView.setCallback(
new DelegateTvInputCallback(mCallback) {
@Override
@@ -338,9 +348,13 @@
void retune() {
if (DEBUG) Log.d(TAG, "Retune requested.");
+ if (mIsDvrSession) {
+ Log.w(TAG, "DVR session should not call retune()!");
+ return;
+ }
if (mNeedToBeRetuned) {
if (DEBUG) Log.d(TAG, "Retuning: {channel=" + mChannel + "}");
- mTunableTvView.tuneTo(mChannel, mParams, mOnTuneListener);
+ ((TunableTvView) mTunableTvView).tuneTo(mChannel, mParams, mOnTuneListener);
mNeedToBeRetuned = false;
}
}
@@ -369,9 +383,13 @@
void resetByRecording() {
mCallback.onVideoUnavailable(
mInputId, TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE);
+ if (mIsDvrSession) {
+ Log.w(TAG, "DVR session should not call resetByRecording()!");
+ return;
+ }
if (mTuned) {
if (DEBUG) Log.d(TAG, "Reset TvView session by recording");
- mTunableTvView.resetByRecording();
+ ((TunableTvView) mTunableTvView).resetByRecording();
reset();
}
mNeedToBeRetuned = true;
@@ -386,22 +404,22 @@
public class RecordingSession {
private final String mInputId;
private Uri mChannelUri;
- private final RecordingCallback mCallback;
+ private final RecordingCallbackCompat mCallback;
private final Handler mHandler;
private volatile long mEndTimeMs;
- private TvRecordingClient mClient;
+ private TvRecordingClientCompat mClient;
private boolean mTuned;
RecordingSession(
String inputId,
String tag,
- RecordingCallback callback,
+ RecordingCallbackCompat callback,
Handler handler,
long endTimeMs) {
mInputId = inputId;
mCallback = callback;
mHandler = handler;
- mClient = new TvRecordingClient(mContext, tag, callback, handler);
+ mClient = new TvRecordingClientCompat(mContext, tag, callback, handler);
mEndTimeMs = endTimeMs;
}
@@ -409,29 +427,26 @@
if (DEBUG) Log.d(TAG, "Release of recording session requested.");
runOnHandler(
mMainThreadHandler,
- new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Releasing of recording session.");
- mTuned = false;
- mClient.release();
- mClient = null;
- for (TvViewSession session : mTvViewSessions) {
- if (DEBUG) {
- Log.d(
- TAG,
- "Finding TvView sessions for retune: {tuned="
- + session.mTuned
- + ", inputId="
- + session.mInputId
- + ", session="
- + session
- + "}");
- }
- if (!session.mTuned && Objects.equals(session.mInputId, mInputId)) {
- session.retune();
- break;
- }
+ () -> {
+ if (DEBUG) Log.d(TAG, "Releasing of recording session.");
+ mTuned = false;
+ mClient.release();
+ mClient = null;
+ for (TvViewSession session : mTvViewSessions) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Finding TvView sessions for retune: {tuned="
+ + session.mTuned
+ + ", inputId="
+ + session.mInputId
+ + ", session="
+ + session
+ + "}");
+ }
+ if (!session.mTuned && Objects.equals(session.mInputId, mInputId)) {
+ session.retune();
+ break;
}
}
});
@@ -441,42 +456,39 @@
public void tune(String inputId, Uri channelUri) {
runOnHandler(
mMainThreadHandler,
- new Runnable() {
- @Override
- public void run() {
- int tunedRecordingSessionCount = getTunedRecordingSessionCount(inputId);
- TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- if (input == null
- || !input.canRecord()
- || input.getTunerCount() <= tunedRecordingSessionCount) {
- runOnHandler(
- mHandler,
- new Runnable() {
- @Override
- public void run() {
- mCallback.onConnectionFailed(inputId);
- }
- });
- return;
- }
- mTuned = true;
- int tunedTuneSessionCount = getTunedTvViewSessionCount(inputId);
- if (!isTunedForTvView(channelUri)
- && tunedTuneSessionCount > 0
- && tunedRecordingSessionCount + tunedTuneSessionCount
- >= input.getTunerCount()) {
- for (TvViewSession session : mTvViewSessions) {
- if (session.mTuned
- && Objects.equals(session.mInputId, inputId)
- && !isTunedForRecording(session.mChannelUri)) {
- session.resetByRecording();
- break;
- }
+ () -> {
+ int tunedRecordingSessionCount = getTunedRecordingSessionCount(inputId);
+ TvInputInfo input = mInputManager.getTvInputInfo(inputId);
+ if (input == null
+ || !input.canRecord()
+ || input.getTunerCount() <= tunedRecordingSessionCount) {
+ runOnHandler(
+ mHandler,
+ new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onConnectionFailed(inputId);
+ }
+ });
+ return;
+ }
+ mTuned = true;
+ int tunedTuneSessionCount = getTunedTvViewSessionCount(inputId);
+ if (!isTunedForTvView(channelUri)
+ && tunedTuneSessionCount > 0
+ && tunedRecordingSessionCount + tunedTuneSessionCount
+ >= input.getTunerCount()) {
+ for (TvViewSession session : mTvViewSessions) {
+ if (session.mTuned
+ && Objects.equals(session.mInputId, inputId)
+ && !isTunedForRecording(session.mChannelUri)) {
+ session.resetByRecording();
+ break;
}
}
- mChannelUri = channelUri;
- mClient.tune(inputId, channelUri);
}
+ mChannelUri = channelUri;
+ mClient.tune(inputId, channelUri);
});
}
@@ -504,10 +516,10 @@
}
}
- private static class DelegateTvInputCallback extends TvInputCallback {
- private final TvInputCallback mDelegate;
+ private static class DelegateTvInputCallback extends TvInputCallbackCompat {
+ private final TvInputCallbackCompat mDelegate;
- DelegateTvInputCallback(TvInputCallback delegate) {
+ DelegateTvInputCallback(TvInputCallbackCompat delegate) {
mDelegate = delegate;
}
@@ -565,6 +577,11 @@
public void onTimeShiftStatusChanged(String inputId, int status) {
mDelegate.onTimeShiftStatusChanged(inputId, status);
}
+
+ @Override
+ public void onSignalStrength(String inputId, int value) {
+ mDelegate.onSignalStrength(inputId, value);
+ }
}
/** Called when the {@link TvView} channel is changed. */
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index 94a86cc..b4cf71d 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -22,7 +22,6 @@
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
@@ -49,6 +48,7 @@
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -65,16 +65,22 @@
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;
import com.android.tv.analytics.Tracker;
+import com.android.tv.audio.AudioManagerHelper;
+import com.android.tv.audiotvservice.AudioOnlyTvServiceUtil;
import com.android.tv.common.BuildConfig;
+import com.android.tv.common.CommonConstants;
import com.android.tv.common.CommonPreferences;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.TvContentRatingCache;
import com.android.tv.common.WeakHandler;
+import com.android.tv.common.compat.TvInputInfoCompat;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.memory.MemoryManageable;
+import com.android.tv.common.singletons.HasSingletons;
import com.android.tv.common.ui.setup.OnActionClickListener;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.common.util.ContentUriUtils;
@@ -99,17 +105,19 @@
import com.android.tv.dvr.recorder.ConflictChecker;
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.features.TvFeatures;
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.EventNames;
-import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.perf.TimerEvent;
+import com.android.tv.perf.PerformanceMonitorManagerFactory;
+import com.android.tv.receiver.AudioCapabilitiesReceiver;
import com.android.tv.recommendation.ChannelPreviewUpdater;
import com.android.tv.recommendation.NotificationService;
import com.android.tv.search.ProgramGuideSearchFragment;
+import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
import com.android.tv.ui.ChannelBannerView;
+import com.android.tv.ui.DetailsActivity;
import com.android.tv.ui.InputBannerView;
import com.android.tv.ui.KeypadChannelSwitchView;
import com.android.tv.ui.SelectInputView;
@@ -128,6 +136,7 @@
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment;
import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.AsyncDbTask.DbExecutor;
import com.android.tv.util.CaptionSettings;
import com.android.tv.util.OnboardingUtils;
import com.android.tv.util.RecurringRunner;
@@ -140,6 +149,10 @@
import com.android.tv.util.account.AccountHelper;
import com.android.tv.util.images.ImageCache;
+import com.google.common.base.Optional;
+import dagger.android.AndroidInjection;
+import dagger.android.ContributesAndroidInjector;
+import com.android.tv.common.flags.BackendKnobsFlags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
@@ -150,11 +163,21 @@
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. */
-public class MainActivity extends Activity implements OnActionClickListener, OnPinCheckedListener {
+public class MainActivity extends Activity
+ implements OnActionClickListener,
+ OnPinCheckedListener,
+ ChannelChanger,
+ HasSingletons<MySingletons> {
private static final String TAG = "MainActivity";
private static final boolean DEBUG = false;
+ private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
+
+ /** Singletons needed for this class. */
+ public interface MySingletons extends ChannelBannerView.MySingletons {}
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -175,6 +198,8 @@
private static final float FRAME_RATE_FOR_FILM = 23.976f;
private static final float FRAME_RATE_EPSILON = 0.1f;
+// AOSP_Comment_Out private static final String PLUTO_TV_PACKAGE_NAME = "tv.pluto.android";
+
private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
@@ -232,10 +257,17 @@
private static final int UNDEFINED_TRACK_INDEX = -1;
private static final long START_UP_TIMER_RESET_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(3);
+ {
+ PerformanceMonitorManagerFactory.create().getStartupMeasure().onActivityInit();
+ }
+
+ private final MySingletonsImpl mMySingletons = new MySingletonsImpl();
+ @Inject @DbExecutor Executor mDbExecutor;
+
private AccessibilityManager mAccessibilityManager;
- private ChannelDataManager mChannelDataManager;
- private ProgramDataManager mProgramDataManager;
- private TvInputManagerHelper mTvInputManagerHelper;
+ @Inject ChannelDataManager mChannelDataManager;
+ @Inject ProgramDataManager mProgramDataManager;
+ @Inject TvInputManagerHelper mTvInputManagerHelper;
private ChannelTuner mChannelTuner;
private final TvOptionsManager mTvOptionsManager = new TvOptionsManager(this);
private TvViewUiManager mTvViewUiManager;
@@ -245,10 +277,12 @@
private final DurationTimer mTuneDurationTimer = new DurationTimer();
private DvrManager mDvrManager;
private ConflictChecker mDvrConflictChecker;
- private SetupUtils mSetupUtils;
+ @Inject BackendKnobsFlags mBackendKnobs;
+ @Inject SetupUtils mSetupUtils;
+ @Inject Optional<BuiltInTunerManager> mOptionalBuiltInTunerManager;
+ @VisibleForTesting protected TunableTvView mTvView;
private View mContentView;
- private TunableTvView mTvView;
private Bundle mTuneParams;
@Nullable private Uri mInitChannelUri;
@Nullable private String mParentInputIdWhenScreenOff;
@@ -274,9 +308,7 @@
private boolean mNeedShowBackKeyGuide;
private boolean mVisibleBehind;
private boolean mShowNewSourcesFragment = true;
- private String mTunerInputId;
private boolean mOtherActivityLaunched;
- private PerformanceMonitor mPerformanceMonitor;
private boolean mIsInPIPMode;
private boolean mIsFilmModeSet;
@@ -304,6 +336,8 @@
private RecurringRunner mSendConfigInfoRecurringRunner;
private RecurringRunner mChannelStatusRecurringRunner;
+ private String mLastInputIdFromIntent;
+
private final Handler mHandler = new MainActivityHandler(this);
private final Set<OnActionClickListener> mOnActionClickListeners = new ArraySet<>();
@@ -399,28 +433,27 @@
public void onChannelChanged(Channel previousChannel, Channel currentChannel) {}
};
- private final Runnable mRestoreMainViewRunnable =
- new Runnable() {
- @Override
- public void run() {
- restoreMainTvView();
- }
- };
+ private final Runnable mRestoreMainViewRunnable = this::restoreMainTvView;
private ProgramGuideSearchFragment mSearchFragment;
private final TvInputCallback mTvInputCallback =
new TvInputCallback() {
@Override
public void onInputAdded(String inputId) {
- if (TvFeatures.TUNER.isEnabled(MainActivity.this)
- && mTunerInputId.equals(inputId)
+ if (mOptionalBuiltInTunerManager.isPresent()
&& CommonPreferences.shouldShowSetupActivity(MainActivity.this)) {
- Intent intent =
- TvSingletons.getSingletons(MainActivity.this)
- .getTunerSetupIntent(MainActivity.this);
- startActivity(intent);
- CommonPreferences.setShouldShowSetupActivity(MainActivity.this, false);
- mSetupUtils.markAsKnownInput(mTunerInputId);
+ BuiltInTunerManager builtInTunerManager =
+ mOptionalBuiltInTunerManager.get();
+ String tunerInputId = builtInTunerManager.getEmbeddedTunerInputId();
+ if (tunerInputId.equals(inputId)) {
+ Intent intent =
+ builtInTunerManager
+ .getTunerInputController()
+ .createSetupIntent(MainActivity.this);
+ startActivity(intent);
+ CommonPreferences.setShouldShowSetupActivity(MainActivity.this, false);
+ mSetupUtils.markAsKnownInput(tunerInputId);
+ }
}
}
};
@@ -435,12 +468,16 @@
}
@Override
+ public MySingletons singletons() {
+ return mMySingletons;
+ }
+
+ @Override
protected void onCreate(Bundle savedInstanceState) {
+ AndroidInjection.inject(this);
mAccessibilityManager =
(AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
TvSingletons tvSingletons = TvSingletons.getSingletons(this);
- mPerformanceMonitor = tvSingletons.getPerformanceMonitor();
- TimerEvent timer = mPerformanceMonitor.startTimer();
DurationTimer startUpDebugTimer = Debug.getTimer(Debug.TAG_START_UP_TIMER);
if (!startUpDebugTimer.isStarted()
|| startUpDebugTimer.getDuration() > START_UP_TIMER_RESET_THRESHOLD_MS) {
@@ -454,16 +491,13 @@
}
Starter.start(this);
super.onCreate(savedInstanceState);
- if (!tvSingletons.getTvInputManagerHelper().hasTvInputManager()) {
+ if (!mTvInputManagerHelper.hasTvInputManager()) {
Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
finishAndRemoveTask();
return;
}
- mPerformanceMonitor = tvSingletons.getPerformanceMonitor();
- mSetupUtils = tvSingletons.getSetupUtils();
- TvApplication tvApplication = (TvApplication) getApplication();
- mChannelDataManager = tvApplication.getChannelDataManager();
+ TvSingletons tvApplication = (TvSingletons) getApplication();
// In API 23, TvContract.isChannelUriForPassthroughInput is hidden.
boolean isPassthroughInput =
TvContract.isChannelUriForPassthroughInput(getIntent().getData());
@@ -480,17 +514,12 @@
return;
}
setContentView(R.layout.activity_tv);
- mProgramDataManager = tvApplication.getProgramDataManager();
- mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view);
mTvView.initialize(mProgramDataManager, mTvInputManagerHelper);
mTvView.setOnUnhandledInputEventListener(
new OnUnhandledInputEventListener() {
@Override
public boolean onUnhandledInputEvent(InputEvent event) {
- if (DEBUG) {
- Log.d(TAG, "onUnhandledInputEvent " + event);
- }
if (isKeyEventBlocked()) {
return true;
}
@@ -511,7 +540,7 @@
return false;
}
});
- mTvView.setOnTalkBackDpadKeyListener(keycode -> handleUpDownKeys(keycode, null));
+ mTvView.setBlockedInfoOnClickListener(v -> showPinDialogFragment());
long channelId = Utils.getLastWatchedChannelId(this);
String inputId = Utils.getLastWatchedTunerInputId(this);
if (!isPassthroughInput
@@ -525,10 +554,9 @@
Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
}
mTracker = tvApplication.getTracker();
- if (TvFeatures.TUNER.isEnabled(this)) {
+ if (mOptionalBuiltInTunerManager.isPresent()) {
mTvInputManagerHelper.addCallback(mTvInputCallback);
}
- mTunerInputId = tvSingletons.getEmbeddedTunerInputId();
mProgramDataManager.addOnCurrentProgramUpdatedListener(
Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
mProgramDataManager.setPrefetchEnabled(true);
@@ -657,6 +685,8 @@
mAccessibilityManager.addAccessibilityStateChangeListener(mOverlayManager);
mAudioManagerHelper = new AudioManagerHelper(this, mTvView);
+ mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, null);
+ mAudioCapabilitiesReceiver.register();
Intent nowPlayingIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, REQUEST_CODE_NOW_PLAYING, nowPlayingIntent, 0);
@@ -687,7 +717,6 @@
}
initForTest();
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end");
- mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONCREATE);
}
private void startOnboardingActivity() {
@@ -778,7 +807,6 @@
@Override
protected void onStart() {
- TimerEvent timer = mPerformanceMonitor.startTimer();
if (DEBUG) {
Log.d(TAG, "onStart()");
}
@@ -796,15 +824,17 @@
notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
startService(notificationIntent);
}
- TvSingletons singletons = TvSingletons.getSingletons(this);
- singletons.getTunerInputController().executeNetworkTunerDiscoveryAsyncTask(this);
- singletons.getEpgFetcher().fetchImmediatelyIfNeeded();
- mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONSTART);
+ if (mOptionalBuiltInTunerManager.isPresent()) {
+ mOptionalBuiltInTunerManager
+ .get()
+ .getTunerInputController()
+ .executeNetworkTunerDiscoveryAsyncTask(this);
+ }
+ TvSingletons.getSingletons(this).getEpgFetcher().fetchImmediatelyIfNeeded();
}
@Override
protected void onResume() {
- TimerEvent timer = mPerformanceMonitor.startTimer();
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume start");
if (DEBUG) Log.d(TAG, "onResume()");
super.onResume();
@@ -836,13 +866,9 @@
getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)
&& !failedScheduledRecordingInfoSet.isEmpty()) {
runAfterAttachedToWindow(
- new Runnable() {
- @Override
- public void run() {
+ () ->
DvrUiHelper.showDvrInsufficientSpaceErrorDialog(
- MainActivity.this, failedScheduledRecordingInfoSet);
- }
- });
+ MainActivity.this, failedScheduledRecordingInfoSet));
}
if (mChannelTuner.areAllChannelsLoaded()) {
@@ -861,32 +887,23 @@
// This will delay the start of the animation until after the Live Channel app is
// shown. Without this the animation is completed before it is actually visible on
// the screen.
- mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- mOverlayManager.showProgramGuide();
- }
- });
+ mHandler.post(() -> mOverlayManager.showProgramGuide());
} else if (mShowSelectInputView) {
mShowSelectInputView = false;
// mShowSelectInputView is true when the activity is started/resumed because the
// TV_INPUT button was pressed in a different app. This will delay the start of
// the animation until after the Live Channel app is shown. Without this the
// animation is completed before it is actually visible on the screen.
- mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- mOverlayManager.showSelectInputView();
- }
- });
+ mHandler.post(() -> mOverlayManager.showSelectInputView());
}
if (mDvrConflictChecker != null) {
mDvrConflictChecker.start();
}
+ if (CommonFeatures.ENABLE_TV_SERVICE.isEnabled(this) && isAudioOnlyInput()) {
+ // TODO(b/110969180): figure out when to call AudioOnlyTvServiceUtil.stopAudioOnlyInput
+ AudioOnlyTvServiceUtil.startAudioOnlyInput(this, mLastInputIdFromIntent);
+ }
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume end");
- mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONRESUME);
}
@Override
@@ -913,7 +930,6 @@
} else {
mTracker.sendScreenView(SCREEN_BEHIND_NAME);
}
- TvSingletons.getSingletons(this).getExperimentLoader().asyncRefreshExperiments(this);
super.onPause();
}
@@ -1068,6 +1084,9 @@
markCurrentChannelDuringScreenOff();
}
}
+ if (mChannelTuner.isCurrentChannelPassthrough()) {
+ mInitChannelUri = mChannelTuner.getCurrentChannelUri();
+ }
mActivityStarted = false;
stopAll(false);
unregisterReceiver(mBroadcastReceiver);
@@ -1299,19 +1318,15 @@
if (!Objects.equals(mTvView.getCurrentChannel(), returnChannel)) {
final Channel channel = returnChannel;
Runnable tuneAction =
- new Runnable() {
- @Override
- public void run() {
- tuneToChannel(channel);
- if (mChannelBeforeShrunkenTvView == null
- || !mChannelBeforeShrunkenTvView.equals(channel)) {
- Utils.setLastWatchedChannel(MainActivity.this, channel);
- }
- mIsCompletingShrunkenTvView = false;
- mIsCurrentChannelUnblockedByUser =
- mWasChannelUnblockedBeforeShrunkenByUser;
- mTvView.setBlockScreenType(getDesiredBlockScreenType());
+ () -> {
+ tuneToChannel(channel);
+ if (mChannelBeforeShrunkenTvView == null
+ || !mChannelBeforeShrunkenTvView.equals(channel)) {
+ Utils.setLastWatchedChannel(MainActivity.this, channel);
}
+ mIsCompletingShrunkenTvView = false;
+ mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
+ mTvView.setBlockScreenType(getDesiredBlockScreenType());
};
mTvViewUiManager.fadeOutTvView(tuneAction);
// Will automatically fade-in when video becomes available.
@@ -1423,17 +1438,12 @@
/** Notifies the key input focus is changed to the TV view. */
public void updateKeyInputFocus() {
- mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- mTvView.setBlockScreenType(getDesiredBlockScreenType());
- }
- });
+ mHandler.post(() -> mTvView.setBlockScreenType(getDesiredBlockScreenType()));
}
// It should be called before onResume.
private boolean handleIntent(Intent intent) {
+ mLastInputIdFromIntent = getInputId(intent);
// Reset the closed caption settings when the activity is 1)created or 2) restarted.
// And do not reset while TvView is playing.
if (!mTvView.isPlaying()) {
@@ -1455,13 +1465,7 @@
}
if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) {
- runAfterAttachedToWindow(
- new Runnable() {
- @Override
- public void run() {
- mOverlayManager.showSetupFragment();
- }
- });
+ runAfterAttachedToWindow(() -> mOverlayManager.showSetupFragment());
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
if (Utils.isProgramsUri(uri)) {
@@ -1497,8 +1501,7 @@
long channelIdFromIntent = ContentUriUtils.safeParseId(mInitChannelUri);
if (programUriFromIntent != null && channelIdFromIntent != Channel.INVALID_ID) {
new AsyncQueryProgramTask(
- TvSingletons.getSingletons(this).getDbExecutor(),
- getContentResolver(),
+ mDbExecutor,
programUriFromIntent,
Program.PROJECTION,
null,
@@ -1565,14 +1568,13 @@
public AsyncQueryProgramTask(
Executor executor,
- ContentResolver contentResolver,
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String orderBy,
long channelId) {
- super(executor, contentResolver, uri, projection, selection, selectionArgs, orderBy);
+ super(executor, MainActivity.this, uri, projection, selection, selectionArgs, orderBy);
mChannelIdFromIntent = channelId;
}
@@ -1593,26 +1595,12 @@
}
Channel channel = mChannelDataManager.getChannel(mChannelIdFromIntent);
if (channel != null) {
- ScheduledRecording scheduledRecording =
- TvSingletons.getSingletons(MainActivity.this)
- .getDvrDataManager()
- .getScheduledRecordingForProgramId(program.getId());
- DvrUiHelper.checkStorageStatusAndShowErrorMessage(
- MainActivity.this,
- channel.getInputId(),
- new Runnable() {
- @Override
- public void run() {
- if (CommonFeatures.DVR.isEnabled(MainActivity.this)
- && scheduledRecording == null
- && mDvrManager.isProgramRecordable(program)) {
- DvrUiHelper.requestRecordingFutureProgram(
- MainActivity.this, program, false);
- } else {
- DvrUiHelper.showProgramInfoDialog(MainActivity.this, program);
- }
- }
- });
+ 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.INPUT_ID, channel.getInputId());
+ startActivity(intent);
}
}
}
@@ -1671,6 +1659,11 @@
return;
}
mTunePending = false;
+ if (CommonFeatures.TUNER_SIGNAL_STRENGTH.isEnabled(this)) {
+ mTvView.resetChannelSignalStrength();
+ mOverlayManager.updateChannelBannerAndShowIfNeeded(
+ TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_SIGNAL_STRENGTH);
+ }
final Channel channel = mChannelTuner.getCurrentChannel();
SoftPreconditions.checkState(channel != null);
if (channel == null) {
@@ -1717,18 +1710,14 @@
&& mSetupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) {
// Show new channel sources fragment.
runAfterAttachedToWindow(
- new Runnable() {
- @Override
- public void run() {
+ () ->
mOverlayManager.runAfterOverlaysAreClosed(
new Runnable() {
@Override
public void run() {
mOverlayManager.showNewSourcesFragment();
}
- });
- }
- });
+ }));
}
mSetupUtils.onTuned();
if (mTuneParams != null) {
@@ -1799,12 +1788,9 @@
// should be closed when the activity is paused.
private void runAfterAttachedToWindow(final Runnable runnable) {
final Runnable runOnlyIfActivityIsResumed =
- new Runnable() {
- @Override
- public void run() {
- if (mActivityResumed) {
- runnable.run();
- }
+ () -> {
+ if (mActivityResumed) {
+ runnable.run();
}
};
if (mContentView.isAttachedToWindow()) {
@@ -1918,25 +1904,36 @@
window.setAttributes(layoutParams);
}
- private void applyMultiAudio() {
+ @VisibleForTesting
+ protected void applyMultiAudio(String trackId) {
List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
if (tracks == null) {
mTvOptionsManager.onMultiAudioChanged(null);
return;
}
- String id = TvSettings.getMultiAudioId(this);
- String language = TvSettings.getMultiAudioLanguage(this);
- int channelCount = TvSettings.getMultiAudioChannelCount(this);
- TvTrackInfo bestTrack =
- TvTrackInfoUtils.getBestTrackInfo(tracks, id, language, channelCount);
+ TvTrackInfo bestTrack = null;
+ if (trackId != null) {
+ for (TvTrackInfo track : tracks) {
+ if (trackId.equals(track.getId())) {
+ bestTrack = track;
+ break;
+ }
+ }
+ }
+ if (bestTrack == null) {
+ String id = TvSettings.getMultiAudioId(this);
+ String language = TvSettings.getMultiAudioLanguage(this);
+ int channelCount = TvSettings.getMultiAudioChannelCount(this);
+ bestTrack = TvTrackInfoUtils.getBestTrackInfo(tracks, id, language, channelCount);
+ }
if (bestTrack != null) {
String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO);
if (!bestTrack.getId().equals(selectedTrack)) {
selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack, UNDEFINED_TRACK_INDEX);
} else {
mTvOptionsManager.onMultiAudioChanged(
- Utils.getMultiAudioString(this, bestTrack, false));
+ TvTrackInfoUtils.getMultiAudioString(this, bestTrack, false));
}
return;
}
@@ -2056,8 +2053,8 @@
if (mMediaSessionWrapper != null) {
mMediaSessionWrapper.release();
}
- if (mAudioManagerHelper != null) {
- mAudioManagerHelper.release();
+ if (mAudioCapabilitiesReceiver != null) {
+ mAudioCapabilitiesReceiver.unregister();
}
mHandler.removeCallbacksAndMessages(null);
application.getMainActivityWrapper().onMainActivityDestroyed(this);
@@ -2071,7 +2068,7 @@
}
if (mTvInputManagerHelper != null) {
mTvInputManagerHelper.clearTvInputLabels();
- if (TvFeatures.TUNER.isEnabled(this)) {
+ if (mOptionalBuiltInTunerManager.isPresent()) {
mTvInputManagerHelper.removeCallback(mTvInputCallback);
}
}
@@ -2100,51 +2097,59 @@
if (!mChannelTuner.areAllChannelsLoaded()) {
return false;
}
- if (handleUpDownKeys(keyCode, event)) {
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- private boolean handleUpDownKeys(int keyCode, @Nullable KeyEvent event) {
if (!mChannelTuner.isCurrentChannelPassthrough()) {
switch (keyCode) {
case KeyEvent.KEYCODE_CHANNEL_UP:
case KeyEvent.KEYCODE_DPAD_UP:
- if ((event == null || event.getRepeatCount() == 0)
+ if (event.getRepeatCount() == 0
&& mChannelTuner.getBrowsableChannelCount() > 0) {
- // message sending should be done before moving channel, because we use the
- // existence of message to decide if users are switching channel.
- if (event != null) {
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(
- MSG_CHANNEL_UP_PRESSED, System.currentTimeMillis()),
- CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
- }
- moveToAdjacentChannel(true, false);
- mTracker.sendChannelUp();
+
+ channelUpPressed();
}
return true;
case KeyEvent.KEYCODE_CHANNEL_DOWN:
case KeyEvent.KEYCODE_DPAD_DOWN:
- if ((event == null || event.getRepeatCount() == 0)
+ if (event.getRepeatCount() == 0
&& mChannelTuner.getBrowsableChannelCount() > 0) {
- // message sending should be done before moving channel, because we use the
- // existence of message to decide if users are switching channel.
- if (event != null) {
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(
- MSG_CHANNEL_DOWN_PRESSED, System.currentTimeMillis()),
- CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
- }
- moveToAdjacentChannel(false, false);
- mTracker.sendChannelDown();
+ channelDownPressed();
}
return true;
default: // fall out
}
}
- return false;
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void channelDown() {
+ channelDownPressed();
+ finishChannelChangeIfNeeded();
+ }
+
+ private void channelDownPressed() {
+ // message sending should be done before moving channel, because we use the
+ // existence of message to decide if users are switching channel.
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_CHANNEL_DOWN_PRESSED, System.currentTimeMillis()),
+ CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
+ moveToAdjacentChannel(false, false);
+ mTracker.sendChannelDown();
+ }
+
+ @Override
+ public void channelUp() {
+ channelUpPressed();
+ finishChannelChangeIfNeeded();
+ }
+
+ private void channelUpPressed() {
+ // message sending should be done before moving channel, because we use the
+ // existence of message to decide if users are switching channel.
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_CHANNEL_UP_PRESSED, System.currentTimeMillis()),
+ CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
+ moveToAdjacentChannel(true, false);
+ mTracker.sendChannelUp();
}
@Override
@@ -2228,24 +2233,7 @@
this, mChannelTuner.getCurrentChannel());
return true;
}
- if (!PermissionUtils.hasModifyParentalControls(this)) {
- return true;
- }
- PinDialogFragment dialog = null;
- if (mTvView.isScreenBlocked()) {
- dialog =
- PinDialogFragment.create(
- PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL);
- } else if (mTvView.isContentBlocked()) {
- dialog =
- PinDialogFragment.create(
- PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
- mTvView.getBlockedContentRating().flattenToString());
- }
- if (dialog != null) {
- mOverlayManager.showDialogFragment(
- PinDialogFragment.DIALOG_TAG, dialog, false);
- }
+ showPinDialogFragment();
return true;
case KeyEvent.KEYCODE_WINDOW:
enterPictureInPictureMode();
@@ -2315,16 +2303,12 @@
DvrUiHelper.checkStorageStatusAndShowErrorMessage(
this,
currentChannel.getInputId(),
- new Runnable() {
- @Override
- public void run() {
+ () ->
DvrUiHelper.requestRecordingCurrentProgram(
MainActivity.this,
currentChannel,
program,
- false);
- }
- });
+ false));
}
} else {
DvrUiHelper.showStopRecordingDialog(
@@ -2391,6 +2375,24 @@
return super.onKeyUp(keyCode, event);
}
+ private void showPinDialogFragment() {
+ if (!PermissionUtils.hasModifyParentalControls(this)) {
+ return;
+ }
+ PinDialogFragment dialog = null;
+ if (mTvView.isScreenBlocked()) {
+ dialog = PinDialogFragment.create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL);
+ } else if (mTvView.isContentBlocked()) {
+ dialog =
+ PinDialogFragment.create(
+ PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
+ mTvView.getBlockedContentRating().flattenToString());
+ }
+ if (dialog != null) {
+ mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog, false);
+ }
+ }
+
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "onKeyLongPress(" + event);
@@ -2423,13 +2425,7 @@
mIsInPIPMode = true;
if (mOverlayManager.isOverlayOpened()) {
mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
- mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- MainActivity.super.enterPictureInPictureMode();
- }
- });
+ mHandler.post(MainActivity.super::enterPictureInPictureMode);
} else {
MainActivity.super.enterPictureInPictureMode();
}
@@ -2586,7 +2582,9 @@
mTvView.selectTrack(type, track == null ? null : track.getId());
if (type == TvTrackInfo.TYPE_AUDIO) {
mTvOptionsManager.onMultiAudioChanged(
- track == null ? null : Utils.getMultiAudioString(this, track, false));
+ track == null
+ ? null
+ : TvTrackInfoUtils.getMultiAudioString(this, track, false));
} else if (type == TvTrackInfo.TYPE_SUBTITLE) {
mTvOptionsManager.onClosedCaptionsChanged(track, trackIndex);
}
@@ -2594,7 +2592,7 @@
public void selectAudioTrack(String trackId) {
saveMultiAudioSetting(trackId);
- applyMultiAudio();
+ applyMultiAudio(trackId);
}
private void saveMultiAudioSetting(String trackId) {
@@ -2657,6 +2655,13 @@
case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
return;
+ case CommonConstants.VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED:
+ Toast.makeText(
+ this,
+ R.string.msg_channel_unavailable_not_connected,
+ Toast.LENGTH_SHORT)
+ .show();
+ break;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
default:
Toast.makeText(this, R.string.msg_channel_unavailable_unknown, Toast.LENGTH_SHORT)
@@ -2725,14 +2730,11 @@
mLazyInitialized = true;
// Running initialization.
mHandler.postDelayed(
- new Runnable() {
- @Override
- public void run() {
- if (mActivityStarted) {
- initAnimations();
- initSideFragments();
- initMenuItemViews();
- }
+ () -> {
+ if (mActivityStarted) {
+ initAnimations();
+ initSideFragments();
+ initMenuItemViews();
}
},
LAZY_INITIALIZATION_DELAY);
@@ -2751,6 +2753,23 @@
mOverlayManager.getMenu().preloadItemViews();
}
+ private boolean isAudioOnlyInput() {
+ if (mLastInputIdFromIntent == null) {
+ return false;
+ }
+ TvInputInfoCompat inputInfo =
+ mTvInputManagerHelper.getTvInputInfoCompat(mLastInputIdFromIntent);
+ return inputInfo != null && inputInfo.isAudioOnly();
+ }
+
+ @Nullable
+ private String getInputId(Intent intent) {
+ Uri uri = intent.getData();
+ return TvContract.isChannelUriForPassthroughInput(uri)
+ ? uri.getPathSegments().get(1)
+ : null;
+ }
+
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
@@ -2793,15 +2812,22 @@
}
}
- private class MyOnTuneListener implements OnTuneListener {
+ /** {@link OnTuneListener} implementation */
+ @VisibleForTesting
+ protected class MyOnTuneListener implements OnTuneListener {
boolean mUnlockAllowedRatingBeforeShrunken = true;
boolean mWasUnderShrunkenTvView;
Channel mChannel;
- private void onTune(Channel channel, boolean wasUnderShrukenTvView) {
+ private void onTune(Channel channel, boolean wasUnderShrunkenTvView) {
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.MyOnTuneListener.onTune");
mChannel = channel;
- mWasUnderShrunkenTvView = wasUnderShrukenTvView;
+ mWasUnderShrunkenTvView = wasUnderShrunkenTvView;
+
+ if (mBackendKnobs.enablePartialProgramFetch()) {
+ // Fetch complete projection of tuned channel.
+ mProgramDataManager.prefetchChannel(channel.getId());
+ }
}
@Override
@@ -2824,7 +2850,7 @@
}
@Override
- public void onStreamInfoChanged(StreamInfo info) {
+ public void onStreamInfoChanged(StreamInfo info, boolean allowAutoSelectionOfTrack) {
if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) {
mTracker.sendChannelTuneTime(info.getCurrentChannel(), mTuneDurationTimer.reset());
}
@@ -2834,7 +2860,8 @@
}
applyDisplayRefreshRate(info.getVideoFrameRate());
mTvViewUiManager.updateTvAspectRatio();
- applyMultiAudio();
+ applyMultiAudio(
+ allowAutoSelectionOfTrack ? null : getSelectedTrack(TvTrackInfo.TYPE_AUDIO));
applyClosedCaption();
mOverlayManager.getMenu().onStreamInfoChanged();
if (mTvView.isVideoAvailable()) {
@@ -2861,6 +2888,12 @@
+ channel);
return;
}
+ /* Begin_AOSP_Comment_Out
+ if (PLUTO_TV_PACKAGE_NAME.equals(currentChannel.getPackageName())) {
+ // Do nothing for the Pluto TV input because it misuses this API. b/22720711.
+ return;
+ }
+ End_AOSP_Comment_Out */
if (isChannelChangeKeyDownReceived()) {
// Ignore this message if the user is changing the channel.
return;
@@ -2883,7 +2916,7 @@
// before.
if (mWasUnderShrunkenTvView
&& mUnlockAllowedRatingBeforeShrunken
- && mChannelBeforeShrunkenTvView.equals(mChannel)
+ && Objects.equals(mChannelBeforeShrunkenTvView, mChannel)
&& rating.equals(mAllowedRatingBeforeShrunken)) {
mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView();
mTvView.unblockContent(rating);
@@ -2901,5 +2934,53 @@
mOverlayManager.setBlockingContentRating(null);
mMediaSessionWrapper.update(false, getCurrentChannel(), getCurrentProgram());
}
+
+ @Override
+ public void onChannelSignalStrength() {
+ if (CommonFeatures.TUNER_SIGNAL_STRENGTH.isEnabled(getApplicationContext())) {
+ mOverlayManager.updateChannelBannerAndShowIfNeeded(
+ TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_SIGNAL_STRENGTH);
+ }
+ }
+ }
+
+ private class MySingletonsImpl implements MySingletons {
+
+ @Override
+ public Provider<Channel> getCurrentChannelProvider() {
+ return MainActivity.this::getCurrentChannel;
+ }
+
+ @Override
+ public Provider<Program> getCurrentProgramProvider() {
+ return MainActivity.this::getCurrentProgram;
+ }
+
+ @Override
+ public Provider<TvOverlayManager> getOverlayManagerProvider() {
+ return MainActivity.this::getOverlayManager;
+ }
+
+ @Override
+ public TvInputManagerHelper getTvInputManagerHelperSingleton() {
+ return getTvInputManagerHelper();
+ }
+
+ @Override
+ public Provider<Long> getCurrentPlayingPositionProvider() {
+ return MainActivity.this::getCurrentPlayingPosition;
+ }
+
+ @Override
+ public DvrManager getDvrManagerSingleton() {
+ return TvSingletons.getSingletons(getApplicationContext()).getDvrManager();
+ }
+ }
+
+ /** Exports {@link MainActivity} for Dagger codegen to create the appropriate injector. */
+ @dagger.Module
+ public abstract static class Module {
+ @ContributesAndroidInjector
+ abstract MainActivity contributesMainActivityActivityInjector();
}
}
diff --git a/src/com/android/tv/MediaSessionWrapper.java b/src/com/android/tv/MediaSessionWrapper.java
index 43cd74d..a647a06 100644
--- a/src/com/android/tv/MediaSessionWrapper.java
+++ b/src/com/android/tv/MediaSessionWrapper.java
@@ -16,12 +16,14 @@
package com.android.tv;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadata;
+import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.media.tv.TvContract;
@@ -31,6 +33,7 @@
import android.support.annotation.Nullable;
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.util.Utils;
@@ -41,9 +44,12 @@
* {@link MainActivity}.
*/
class MediaSessionWrapper {
+ private static final String TAG = "MediaSessionWrapper";
+ private static final boolean DEBUG = false;
private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession";
- private static final PlaybackState MEDIA_SESSION_STATE_PLAYING =
+ @VisibleForTesting
+ static final PlaybackState MEDIA_SESSION_STATE_PLAYING =
new PlaybackState.Builder()
.setState(
PlaybackState.STATE_PLAYING,
@@ -51,7 +57,8 @@
1.0f)
.build();
- private static final PlaybackState MEDIA_SESSION_STATE_STOPPED =
+ @VisibleForTesting
+ static final PlaybackState MEDIA_SESSION_STATE_STOPPED =
new PlaybackState.Builder()
.setState(
PlaybackState.STATE_STOPPED,
@@ -61,6 +68,20 @@
private final Context mContext;
private final MediaSession mMediaSession;
+ private final MediaController.Callback mMediaControllerCallback =
+ new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(@Nullable PlaybackState state) {
+ super.onPlaybackStateChanged(state);
+ if (DEBUG) {
+ Log.d(TAG, "onPlaybackStateChanged: " + state);
+ }
+ if (isMediaSessionStateStop(state)) {
+ mMediaSession.setActive(false);
+ }
+ }
+ };
+ private MediaController mMediaController;
private int mNowPlayingCardWidth;
private int mNowPlayingCardHeight;
@@ -79,6 +100,8 @@
MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setSessionActivity(pendingIntent);
+
+ initMediaController();
mNowPlayingCardWidth =
mContext.getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width);
mNowPlayingCardHeight =
@@ -97,7 +120,6 @@
mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_PLAYING);
} else if (mMediaSession.isActive()) {
mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_STOPPED);
- mMediaSession.setActive(false);
}
}
@@ -150,6 +172,7 @@
* @see MediaSession#release()
*/
void release() {
+ unregisterMediaControllerCallback();
mMediaSession.release();
}
@@ -223,6 +246,30 @@
return mMediaSession;
}
+ @VisibleForTesting
+ MediaController.Callback getMediaControllerCallback() {
+ return mMediaControllerCallback;
+ }
+
+ @VisibleForTesting
+ void initMediaController() {
+ mMediaController = new MediaController(mContext, mMediaSession.getSessionToken());
+ ((Activity) mContext).setMediaController(mMediaController);
+ mMediaController.registerCallback(mMediaControllerCallback);
+ }
+
+ @VisibleForTesting
+ void unregisterMediaControllerCallback() {
+ mMediaController.unregisterCallback(mMediaControllerCallback);
+ }
+
+ private static boolean isMediaSessionStateStop(PlaybackState state) {
+ return state != null
+ && state.getState() == MEDIA_SESSION_STATE_STOPPED.getState()
+ && state.getPosition() == MEDIA_SESSION_STATE_STOPPED.getPosition()
+ && state.getPlaybackSpeed() == MEDIA_SESSION_STATE_STOPPED.getPlaybackSpeed();
+ }
+
private static class ProgramPosterArtCallback
extends ImageLoader.ImageLoaderCallback<MediaSessionWrapper> {
private final Channel mChannel;
diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java
index 199ea51..5185b12 100644
--- a/src/com/android/tv/SetupPassthroughActivity.java
+++ b/src/com/android/tv/SetupPassthroughActivity.java
@@ -28,11 +28,11 @@
import android.util.Log;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.actions.InputSetupActionUtils;
-import com.android.tv.common.experiments.Experiments;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelDataManager.Listener;
import com.android.tv.data.epg.EpgFetcher;
import com.android.tv.data.epg.EpgInputWhiteList;
+import com.android.tv.features.TvFeatures;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -66,12 +66,10 @@
Intent intent = getIntent();
String inputId = intent.getStringExtra(InputSetupActionUtils.EXTRA_INPUT_ID);
mTvInputInfo = inputManager.getTvInputInfo(inputId);
- mEpgInputWhiteList = new EpgInputWhiteList(tvSingletons.getRemoteConfig());
+ mEpgInputWhiteList = new EpgInputWhiteList(tvSingletons.getCloudEpgFlags());
mActivityAfterCompletion = InputSetupActionUtils.getExtraActivityAfter(intent);
boolean needToFetchEpg =
- mTvInputInfo != null
- && Utils.isInternalTvInput(this, mTvInputInfo.getId())
- && Experiments.CLOUD_EPG.get();
+ mTvInputInfo != null && Utils.isInternalTvInput(this, mTvInputInfo.getId());
if (needToFetchEpg) {
// In case when the activity is restored, this flag should be restored as well.
mEpgFetcherDuringScan = true;
@@ -144,23 +142,30 @@
finish();
return;
}
+ if (mTvInputInfo == null) {
+ Log.w(
+ TAG,
+ "There is no input with ID "
+ + getIntent().getStringExtra(InputSetupActionUtils.EXTRA_INPUT_ID)
+ + ".");
+ setResult(resultCode, data);
+ finish();
+ return;
+ }
TvSingletons.getSingletons(this)
.getSetupUtils()
.onTvInputSetupFinished(
mTvInputInfo.getId(),
- new Runnable() {
- @Override
- public void run() {
- if (mActivityAfterCompletion != null) {
- try {
- startActivity(mActivityAfterCompletion);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "Activity launch failed", e);
- }
+ () -> {
+ if (mActivityAfterCompletion != null) {
+ try {
+ startActivity(mActivityAfterCompletion);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity launch failed", e);
}
- setResult(resultCode, data);
- finish();
}
+ setResult(resultCode, data);
+ finish();
});
}
@@ -178,15 +183,12 @@
private final ChannelDataManager mChannelDataManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Runnable mScanTimeoutRunnable =
- new Runnable() {
- @Override
- public void run() {
- Log.w(
- TAG,
- "No channels has been added for a while."
- + " The scan might have finished unexpectedly.");
- onScanTimedOut();
- }
+ () -> {
+ Log.w(
+ TAG,
+ "No channels has been added for a while."
+ + " The scan might have finished unexpectedly.");
+ onScanTimedOut();
};
private final Listener mChannelDataManagerListener =
new Listener() {
diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java
index bb3574d..779e8df 100644
--- a/src/com/android/tv/TimeShiftManager.java
+++ b/src/com/android/tv/TimeShiftManager.java
@@ -17,7 +17,6 @@
package com.android.tv;
import android.annotation.SuppressLint;
-import android.content.ContentResolver;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
@@ -35,7 +34,7 @@
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.api.Channel;
import com.android.tv.ui.TunableTvView;
-import com.android.tv.ui.TunableTvViewPlayingApi.TimeShiftListener;
+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;
@@ -87,16 +86,15 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(
- flag = true,
- value = {
- TIME_SHIFT_ACTION_ID_PLAY,
- TIME_SHIFT_ACTION_ID_PAUSE,
- TIME_SHIFT_ACTION_ID_REWIND,
- TIME_SHIFT_ACTION_ID_FAST_FORWARD,
- TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS,
- TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT
- }
- )
+ flag = true,
+ value = {
+ TIME_SHIFT_ACTION_ID_PLAY,
+ TIME_SHIFT_ACTION_ID_PAUSE,
+ TIME_SHIFT_ACTION_ID_REWIND,
+ TIME_SHIFT_ACTION_ID_FAST_FORWARD,
+ TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS,
+ TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT
+ })
public @interface TimeShiftActionId {}
public static final int TIME_SHIFT_ACTION_ID_PLAY = 1;
@@ -715,7 +713,7 @@
: mRecordEndTimeMs;
long currentPositionMs =
Math.max(
- Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs),
+ Math.min(mTvView.timeShiftGetCurrentPositionMs(), currentTimeMs),
mRecordStartTimeMs);
boolean isCurrentTime =
currentTimeMs - currentPositionMs < RECORDING_BOUNDARY_THRESHOLD;
@@ -723,7 +721,7 @@
if (isCurrentTime && isForwarding()) {
// It's playing forward and the current playing position reached
// the current system time. i.e. The live stream is played.
- // Therefore no need to call TvView.timeshiftGetCurrentPositionMs
+ // Therefore no need to call TvView.timeShiftGetCurrentPositionMs
// any more.
newCurrentPositionMs = currentTimeMs;
mIsPlayOffsetChanged = false;
@@ -753,14 +751,14 @@
mDisplayedPlaySpeed = PLAY_SPEED_1X;
mPlaybackSpeed = 1;
mPlayDirection = PLAY_DIRECTION_FORWARD;
- mTvView.timeshiftPlay();
+ mTvView.timeShiftPlay();
setPlayStatus(PLAY_STATUS_PLAYING);
}
void pause() {
mDisplayedPlaySpeed = PLAY_SPEED_1X;
mPlaybackSpeed = 1;
- mTvView.timeshiftPause();
+ mTvView.timeShiftPause();
setPlayStatus(PLAY_STATUS_PAUSED);
mIsPlayOffsetChanged = true;
}
@@ -783,7 +781,7 @@
}
mPlayDirection = PLAY_DIRECTION_BACKWARD;
mPlaybackSpeed = getPlaybackSpeed();
- mTvView.timeshiftRewind(mPlaybackSpeed);
+ mTvView.timeShiftRewind(mPlaybackSpeed);
setPlayStatus(PLAY_STATUS_PLAYING);
mIsPlayOffsetChanged = true;
}
@@ -796,14 +794,14 @@
}
mPlayDirection = PLAY_DIRECTION_FORWARD;
mPlaybackSpeed = getPlaybackSpeed();
- mTvView.timeshiftFastForward(mPlaybackSpeed);
+ mTvView.timeShiftFastForward(mPlaybackSpeed);
setPlayStatus(PLAY_STATUS_PLAYING);
mIsPlayOffsetChanged = true;
}
/** Moves to the specified time. */
void seekTo(long timeMs) {
- mTvView.timeshiftSeekTo(
+ mTvView.timeShiftSeekTo(
Math.min(
mRecordEndTimeMs == CURRENT_TIME
? System.currentTimeMillis()
@@ -821,9 +819,9 @@
if (playbackSpeed != mPlaybackSpeed) {
mPlaybackSpeed = playbackSpeed;
if (mPlayDirection == PLAY_DIRECTION_FORWARD) {
- mTvView.timeshiftFastForward(mPlaybackSpeed);
+ mTvView.timeShiftFastForward(mPlaybackSpeed);
} else {
- mTvView.timeshiftRewind(mPlaybackSpeed);
+ mTvView.timeShiftRewind(mPlaybackSpeed);
}
}
}
@@ -977,8 +975,7 @@
}
}
if (mChannel != null) {
- mProgramLoadTask =
- new LoadProgramsForCurrentChannelTask(mContext.getContentResolver(), next);
+ mProgramLoadTask = new LoadProgramsForCurrentChannelTask(next);
mProgramLoadTask.executeOnDbThread();
}
}
@@ -1225,10 +1222,10 @@
private class LoadProgramsForCurrentChannelTask
extends AsyncDbTask.LoadProgramsForChannelTask {
- LoadProgramsForCurrentChannelTask(ContentResolver contentResolver, Range<Long> period) {
+ LoadProgramsForCurrentChannelTask(Range<Long> period) {
super(
TvSingletons.getSingletons(mContext).getDbExecutor(),
- contentResolver,
+ mContext,
mChannel.getId(),
period);
}
@@ -1309,13 +1306,7 @@
mProgramLoadTask = null;
}
// Need to post to handler, because the task is still running.
- mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- startTaskIfNeeded();
- }
- });
+ mHandler.post(ProgramManager.this::startTaskIfNeeded);
}
boolean overlaps(Queue<Range<Long>> programLoadQueue) {
diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java
index 826317b..5f25a24 100644
--- a/src/com/android/tv/TvApplication.java
+++ b/src/com/android/tv/TvApplication.java
@@ -34,8 +34,8 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
+import android.widget.Toast;
import com.android.tv.common.BaseApplication;
-import com.android.tv.common.concurrent.NamedThreadFactory;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
@@ -55,17 +55,22 @@
import com.android.tv.dvr.DvrWatchedPositionManager;
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.recommendation.ChannelPreviewUpdater;
import com.android.tv.recommendation.RecordedProgramPreviewUpdater;
-import com.android.tv.tuner.TunerInputController;
-import com.android.tv.tuner.util.TunerInputInfoUtils;
+import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
+import com.android.tv.tunerinputcontroller.TunerInputController;
+import com.android.tv.util.AsyncDbTask.DbExecutor;
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 java.util.List;
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import javax.inject.Inject;
/**
* Live TV application.
@@ -73,6 +78,9 @@
* <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();
private static final String TAG = "TvApplication";
private static final boolean DEBUG = false;
@@ -89,10 +97,6 @@
private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch";
- private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory("tv-app-db");
- private static final ExecutorService DB_EXECUTOR =
- Executors.newSingleThreadExecutor(THREAD_FACTORY);
-
private String mVersionName = "";
private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper();
@@ -111,22 +115,28 @@
// STOP-SHIP: Remove this variable when Tuner Process is split to another application.
// When this variable is null, we don't know in which process TvApplication runs.
private Boolean mRunningInMainProcess;
- private TvInputManagerHelper mTvInputManagerHelper;
+ @Inject Lazy<TvInputManagerHelper> mLazyTvInputManagerHelper;
private boolean mStarted;
private EpgFetcher mEpgFetcher;
- private TunerInputController mTunerInputController;
+
+ @Inject Optional<BuiltInTunerManager> mOptionalBuiltInTunerManager;
+ @Inject SetupUtils mSetupUtils;
+ @Inject @DbExecutor Executor mDbExecutor;
@Override
public void onCreate() {
+ if (getSystemService(TvInputManager.class) == null) {
+ String msg = "Not an Android TV device.";
+ Toast.makeText(this, msg, Toast.LENGTH_LONG);
+ Log.wtf(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
super.onCreate();
SharedPreferencesUtils.initialize(
this,
- new Runnable() {
- @Override
- public void run() {
- if (mRunningInMainProcess != null && mRunningInMainProcess) {
- checkTunerServiceOnFirstLaunch();
- }
+ () -> {
+ if (mRunningInMainProcess != null && mRunningInMainProcess) {
+ checkTunerServiceOnFirstLaunch();
}
});
try {
@@ -164,13 +174,19 @@
new TvInputCallback() {
@Override
public void onInputAdded(String inputId) {
- if (TvFeatures.TUNER.isEnabled(TvApplication.this)
- && TextUtils.equals(
- inputId, getEmbeddedTunerInputId())) {
- TunerInputInfoUtils.updateTunerInputInfo(
- TvApplication.this);
+ if (mOptionalBuiltInTunerManager.isPresent()) {
+ BuiltInTunerManager builtInTunerManager =
+ mOptionalBuiltInTunerManager.get();
+ if (TextUtils.equals(
+ inputId,
+ builtInTunerManager.getEmbeddedTunerInputId())) {
+
+ builtInTunerManager
+ .getTunerInputController()
+ .updateTunerInputInfo(TvApplication.this);
+ }
+ handleInputCountChanged();
}
- handleInputCountChanged();
}
@Override
@@ -178,10 +194,13 @@
handleInputCountChanged();
}
});
- if (TvFeatures.TUNER.isEnabled(this)) {
+ if (mOptionalBuiltInTunerManager.isPresent()) {
// If the tuner input service is added before the app is started, we need to
// handle it here.
- TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this);
+ mOptionalBuiltInTunerManager
+ .get()
+ .getTunerInputController()
+ .updateTunerInputInfo(TvApplication.this);
}
if (CommonFeatures.DVR.isEnabled(this)) {
mDvrScheduleManager = new DvrScheduleManager(this);
@@ -205,8 +224,12 @@
boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true);
if (isFirstLaunch) {
if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!");
- getTunerInputController()
- .onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED);
+ if (mOptionalBuiltInTunerManager.isPresent()) {
+ mOptionalBuiltInTunerManager
+ .get()
+ .getTunerInputController()
+ .onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED);
+ }
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false);
editor.apply();
@@ -220,7 +243,7 @@
@Override
public synchronized SetupUtils getSetupUtils() {
- return SetupUtils.createForTvSingletons(this);
+ return mSetupUtils;
}
/** Returns the {@link DvrManager}. */
@@ -282,13 +305,10 @@
return mProgramDataManager;
}
Utils.runInMainThreadAndWait(
- new Runnable() {
- @Override
- public void run() {
- if (mProgramDataManager == null) {
- mProgramDataManager = new ProgramDataManager(TvApplication.this);
- mProgramDataManager.start();
- }
+ () -> {
+ if (mProgramDataManager == null) {
+ mProgramDataManager = new ProgramDataManager(TvApplication.this);
+ mProgramDataManager.start();
}
});
return mProgramDataManager;
@@ -340,21 +360,7 @@
/** Returns {@link TvInputManagerHelper}. */
@Override
public TvInputManagerHelper getTvInputManagerHelper() {
- if (mTvInputManagerHelper == null) {
- mTvInputManagerHelper = new TvInputManagerHelper(this);
- mTvInputManagerHelper.start();
- }
- return mTvInputManagerHelper;
- }
-
- @Override
- public synchronized TunerInputController getTunerInputController() {
- if (mTunerInputController == null) {
- mTunerInputController =
- new TunerInputController(
- ComponentName.unflattenFromString(getEmbeddedTunerInputId()));
- }
- return mTunerInputController;
+ return mLazyTvInputManagerHelper.get();
}
@Override
@@ -480,12 +486,16 @@
if (!enable) {
List<TvInputInfo> inputs = inputManager.getTvInputList();
boolean skipTunerInputCheck = false;
+ Optional<String> optionalEmbeddedTunerInputId =
+ mOptionalBuiltInTunerManager.transform(
+ BuiltInTunerManager::getEmbeddedTunerInputId);
// Enable the TvActivity only if there is at least one tuner type input.
if (!skipTunerInputCheck) {
for (TvInputInfo input : inputs) {
if (calledByTunerServiceChanged
&& !tunerServiceEnabled
- && getEmbeddedTunerInputId().equals(input.getId())) {
+ && optionalEmbeddedTunerInputId.isPresent()
+ && optionalEmbeddedTunerInputId.get().equals(input.getId())) {
continue;
}
if (input.getType() == TvInputInfo.TYPE_TUNER) {
@@ -507,11 +517,11 @@
name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0);
Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV.");
}
- getSetupUtils().onInputListUpdated(inputManager);
+ mSetupUtils.onInputListUpdated(inputManager);
}
@Override
public Executor getDbExecutor() {
- return DB_EXECUTOR;
+ return mDbExecutor;
}
}
diff --git a/src/com/android/tv/TvSingletons.java b/src/com/android/tv/TvSingletons.java
index 0c7f78a..20edf3d 100644
--- a/src/com/android/tv/TvSingletons.java
+++ b/src/com/android/tv/TvSingletons.java
@@ -22,6 +22,7 @@
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;
@@ -33,17 +34,23 @@
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.recorder.RecordingScheduler;
import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.tuner.TunerInputController;
+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 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 {
+public interface TvSingletons extends BaseSingletons, HasBuiltInTunerManager, HasUiFlags {
- /** Returns the @{@link TvSingletons} using the application context. */
+ /**
+ * Returns the @{@link TvSingletons} using the application context.
+ *
+ * @deprecated use injection instead.
+ */
+ @Deprecated
static TvSingletons getSingletons(Context context) {
return (TvSingletons) BaseApplication.getSingletons(context);
}
@@ -52,6 +59,7 @@
void handleInputCountChanged();
+ @Deprecated
ChannelDataManager getChannelDataManager();
/**
@@ -60,6 +68,8 @@
*/
boolean isChannelDataManagerLoadFinished();
+ /** @deprecated use injection instead. */
+ @Deprecated
ProgramDataManager getProgramDataManager();
/**
@@ -92,17 +102,23 @@
PerformanceMonitor getPerformanceMonitor();
+ /** @deprecated use injection instead. */
+ @Deprecated
TvInputManagerHelper getTvInputManagerHelper();
Provider<EpgReader> providesEpgReader();
EpgFetcher getEpgFetcher();
+ /** @deprecated use injection instead. */
+ @Deprecated
SetupUtils getSetupUtils();
- TunerInputController getTunerInputController();
-
ExperimentLoader getExperimentLoader();
+ /** @deprecated use injection instead. */
+ @Deprecated
Executor getDbExecutor();
+
+ BackendKnobsFlags getBackendKnobs();
}
diff --git a/src/com/android/tv/analytics/SendChannelStatusRunnable.java b/src/com/android/tv/analytics/SendChannelStatusRunnable.java
index 4a84434..306bd85 100644
--- a/src/com/android/tv/analytics/SendChannelStatusRunnable.java
+++ b/src/com/android/tv/analytics/SendChannelStatusRunnable.java
@@ -43,13 +43,7 @@
final SendChannelStatusRunnable sendChannelStatusRunnable =
new SendChannelStatusRunnable(channelDataManager, tracker);
- Runnable onStopRunnable =
- new Runnable() {
- @Override
- public void run() {
- sendChannelStatusRunnable.setDbLoadListener(null);
- }
- };
+ Runnable onStopRunnable = () -> sendChannelStatusRunnable.setDbLoadListener(null);
final RecurringRunner recurringRunner =
new RecurringRunner(
context,
@@ -70,14 +64,7 @@
// done
// via a post on the main thread
new Handler(Looper.getMainLooper())
- .post(
- new Runnable() {
- @Override
- public void run() {
- sendChannelStatusRunnable.setDbLoadListener(
- null);
- }
- });
+ .post(() -> sendChannelStatusRunnable.setDbLoadListener(null));
recurringRunner.start();
}
diff --git a/src/com/android/tv/app/LiveTvApplication.java b/src/com/android/tv/app/LiveTvApplication.java
index 461331d..38e85e4 100644
--- a/src/com/android/tv/app/LiveTvApplication.java
+++ b/src/com/android/tv/app/LiveTvApplication.java
@@ -16,36 +16,37 @@
package com.android.tv.app;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.media.tv.TvContract;
import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Analytics;
import com.android.tv.analytics.StubAnalytics;
import com.android.tv.analytics.Tracker;
-import com.android.tv.common.CommonConstants;
-import com.android.tv.common.actions.InputSetupActionUtils;
-import com.android.tv.common.config.DefaultConfigManager;
-import com.android.tv.common.config.api.RemoteConfig;
+import com.android.tv.common.dagger.ApplicationModule;
import com.android.tv.common.experiments.ExperimentLoader;
-import com.android.tv.common.util.CommonUtils;
+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.StubPerformanceMonitor;
-import com.android.tv.tuner.livetuner.LiveTvTunerTvInputService;
-import com.android.tv.tuner.setup.LiveTvTunerSetupActivity;
+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;
/** The top level application for Live TV. */
-public class LiveTvApplication extends TvApplication {
- protected static final String TV_ACTIVITY_CLASS_NAME =
- CommonConstants.BASE_PACKAGE + ".TvActivity";
+public class LiveTvApplication extends TvApplication implements HasSingletons<TvSingletons> {
- private final StubPerformanceMonitor performanceMonitor = new StubPerformanceMonitor();
+ static {
+ PERFORMANCE_MONITOR_MANAGER.getStartupMeasure().onAppClassLoaded();
+ }
+
private final Provider<EpgReader> mEpgReaderProvider =
new Provider<EpgReader>() {
@@ -55,12 +56,30 @@
}
};
+ 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 String mEmbeddedInputId;
- private RemoteConfig mRemoteConfig;
private ExperimentLoader mExperimentLoader;
+ private PerformanceMonitor mPerformanceMonitor;
+
+ @Override
+ protected AndroidInjector<LiveTvApplication> applicationInjector() {
+ return DaggerLiveTvApplicationComponent.builder()
+ .applicationModule(new ApplicationModule(this))
+ .tvSingletonsModule(new TvSingletonsModule(this))
+ .build();
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ PERFORMANCE_MONITOR_MANAGER.getStartupMeasure().onAppCreate(this);
+ }
/** Returns the {@link AccountHelperImpl}. */
@Override
@@ -73,7 +92,10 @@
@Override
public synchronized PerformanceMonitor getPerformanceMonitor() {
- return performanceMonitor;
+ if (mPerformanceMonitor == null) {
+ mPerformanceMonitor = PerformanceMonitorManagerFactory.create().initialize(this);
+ }
+ return mPerformanceMonitor;
}
@Override
@@ -87,6 +109,11 @@
return mExperimentLoader;
}
+ @Override
+ public DefaultBackendKnobsFlags getBackendKnobs() {
+ return mBackendKnobsFlags;
+ }
+
/** Returns the {@link Analytics}. */
@Override
public synchronized Analytics getAnalytics() {
@@ -106,34 +133,32 @@
}
@Override
- public Intent getTunerSetupIntent(Context context) {
- // Make an intent to launch the setup activity of TV tuner input.
- Intent intent =
- CommonUtils.createSetupIntent(
- new Intent(context, LiveTvTunerSetupActivity.class), mEmbeddedInputId);
- intent.putExtra(InputSetupActionUtils.EXTRA_INPUT_ID, mEmbeddedInputId);
- Intent tvActivityIntent = new Intent();
- tvActivityIntent.setComponent(new ComponentName(context, TV_ACTIVITY_CLASS_NAME));
- intent.putExtra(InputSetupActionUtils.EXTRA_ACTIVITY_AFTER_COMPLETION, tvActivityIntent);
- return intent;
+ public DefaultCloudEpgFlags getCloudEpgFlags() {
+ return mCloudEpgFlags;
}
@Override
- public synchronized String getEmbeddedTunerInputId() {
- if (mEmbeddedInputId == null) {
- mEmbeddedInputId =
- TvContract.buildInputId(
- new ComponentName(this, LiveTvTunerTvInputService.class));
- }
- return mEmbeddedInputId;
+ public DefaultUiFlags getUiFlags() {
+ return mUiFlags;
}
@Override
- public RemoteConfig getRemoteConfig() {
- if (mRemoteConfig == null) {
- // No need to synchronize this, it does not hurt to create two and throw one away.
- mRemoteConfig = DefaultConfigManager.createInstance(this).getRemoteConfig();
- }
- return mRemoteConfig;
+ public Optional<BuiltInTunerManager> getBuiltInTunerManager() {
+ return Optional.absent();
+ }
+
+ @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
new file mode 100644
index 0000000..3d3f049
--- /dev/null
+++ b/src/com/android/tv/app/LiveTvApplicationComponent.java
@@ -0,0 +1,26 @@
+/*
+ * 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.app;
+
+import dagger.Component;
+import dagger.android.AndroidInjectionModule;
+import dagger.android.AndroidInjector;
+import javax.inject.Singleton;
+
+/** Dagger component for {@link LiveTvApplication}. */
+@Singleton
+@Component(modules = {AndroidInjectionModule.class, LiveTvModule.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
new file mode 100644
index 0000000..a28749b
--- /dev/null
+++ b/src/com/android/tv/app/LiveTvModule.java
@@ -0,0 +1,33 @@
+/*
+ * 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.app;
+
+import com.android.tv.common.flags.impl.DefaultFlagsModule;
+import com.android.tv.modules.TvApplicationModule;
+import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
+import com.google.common.base.Optional;
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link LiveTvApplication}. */
+@Module(includes = {DefaultFlagsModule.class, TvApplicationModule.class})
+class LiveTvModule {
+
+ @Provides
+ Optional<BuiltInTunerManager> providesBuiltInTunerManager() {
+ return Optional.absent();
+ }
+}
diff --git a/src/com/android/tv/audio/AudioManagerHelper.java b/src/com/android/tv/audio/AudioManagerHelper.java
new file mode 100644
index 0000000..4acff2d
--- /dev/null
+++ b/src/com/android/tv/audio/AudioManagerHelper.java
@@ -0,0 +1,145 @@
+/*
+ * 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.audio;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import com.android.tv.features.TvFeatures;
+import com.android.tv.ui.api.TunableTvViewPlayingApi;
+
+/** A helper class to help {@code Activities} to handle audio-related stuffs. */
+public class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener {
+ private static final float AUDIO_MAX_VOLUME = 1.0f;
+ private static final float AUDIO_MIN_VOLUME = 0.0f;
+ private static final float AUDIO_DUCKING_VOLUME = 0.3f;
+
+ private final Activity mActivity;
+ private final TunableTvViewPlayingApi mTvView;
+ private final AudioManager mAudioManager;
+ @Nullable private final AudioFocusRequest mFocusRequest;
+
+ private int mAudioFocusStatus = AudioManager.AUDIOFOCUS_NONE;
+
+ public AudioManagerHelper(Activity activity, TunableTvViewPlayingApi tvView) {
+ mActivity = activity;
+ mTvView = tvView;
+ mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mFocusRequest =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setAudioAttributes(
+ new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build())
+ .setOnAudioFocusChangeListener(this)
+ // Auto ducking from the system does not mute the TV Input Service.
+ // Using will pause when ducked allows us to set the stream volume
+ // even when we are not pausing.
+ .setWillPauseWhenDucked(true)
+ .build();
+ } else {
+ mFocusRequest = null;
+ }
+ }
+
+ /**
+ * Sets suitable volume to {@link TunableTvViewPlayingApi} according to the current audio focus.
+ *
+ * <p>If the focus status is {@link AudioManager#AUDIOFOCUS_LOSS} or {@link
+ * AudioManager#AUDIOFOCUS_NONE} and the activity is under PIP mode, this method will finish the
+ * activity. Sets suitable volume to {@link TunableTvViewPlayingApi} according to the current
+ * audio focus. If the focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is
+ * under PIP mode, this method will finish the activity.
+ */
+ public void setVolumeByAudioFocusStatus() {
+ if (mTvView.isPlaying()) {
+ switch (mAudioFocusStatus) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ if (mTvView.isTimeShiftAvailable()) {
+ mTvView.timeShiftPlay();
+ } else {
+ mTvView.setStreamVolume(AUDIO_MAX_VOLUME);
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_NONE:
+ case AudioManager.AUDIOFOCUS_LOSS:
+ if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(mActivity)
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+ && mActivity.isInPictureInPictureMode()) {
+ mActivity.finish();
+ break;
+ }
+ // fall through
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ if (mTvView.isTimeShiftAvailable()) {
+ mTvView.timeShiftPause();
+ } else {
+ mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ if (mTvView.isTimeShiftAvailable()) {
+ mTvView.timeShiftPause();
+ } else {
+ mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Tries to request audio focus from {@link AudioManager} and set volume according to the
+ * returned result.
+ */
+ public void requestAudioFocus() {
+ int result;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ result = mAudioManager.requestAudioFocus(mFocusRequest);
+ } else {
+ result =
+ mAudioManager.requestAudioFocus(
+ this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+ }
+ mAudioFocusStatus =
+ (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
+ ? AudioManager.AUDIOFOCUS_GAIN
+ : AudioManager.AUDIOFOCUS_LOSS;
+ setVolumeByAudioFocusStatus();
+ }
+
+ /** Abandons audio focus. */
+ public void abandonAudioFocus() {
+ mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mAudioManager.abandonAudioFocusRequest(mFocusRequest);
+ } else {
+ mAudioManager.abandonAudioFocus(this);
+ }
+ }
+
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ mAudioFocusStatus = focusChange;
+ setVolumeByAudioFocusStatus();
+ }
+}
diff --git a/src/com/android/tv/audiotvservice/AudioOnlyTvService.java b/src/com/android/tv/audiotvservice/AudioOnlyTvService.java
new file mode 100644
index 0000000..5d0e9c8
--- /dev/null
+++ b/src/com/android/tv/audiotvservice/AudioOnlyTvService.java
@@ -0,0 +1,102 @@
+/*
+ * 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.audiotvservice;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Intent;
+import android.media.session.MediaSession;
+import android.net.Uri;
+import android.os.IBinder;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import com.android.tv.data.ChannelImpl;
+import com.android.tv.data.StreamInfo;
+import com.android.tv.data.api.Channel;
+import com.android.tv.ui.TunableTvView;
+import com.android.tv.ui.TunableTvView.OnTuneListener;
+
+/** Foreground service for audio-only TV inputs. */
+public class AudioOnlyTvService extends Service implements OnTuneListener {
+ // TODO(b/110969180): implement this service.
+ private static final String TAG = "AudioOnlyTvService";
+ private static final int NOTIFICATION_ID = 1;
+
+ @Nullable private String mTvInputId;
+ private TunableTvView mTvView;
+ // TODO(b/110969180): perhaps use MediaSessionWrapper
+ private MediaSession mMediaSession;
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind");
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate");
+ // TODO(b/110969180): create TvView
+
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.i(TAG, "onStartCommand. flags = " + flags + ", startId = " + startId);
+ // TODO(b/110969180): real notification and or media session
+ startForeground(NOTIFICATION_ID, new Notification());
+ mTvInputId = AudioOnlyTvServiceUtil.getInputIdFromIntent(intent);
+ tune(mTvInputId);
+ return START_STICKY;
+ }
+
+ private void tune(String tvInputId) {
+ Channel channel = ChannelImpl.createPassthroughChannel(tvInputId);
+ mTvView.tuneTo(channel, null, this);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(TAG, "onDestroy");
+ mTvInputId = null;
+ // TODO(b/110969180): clear TvView
+ }
+
+ // TODO(b/110969180): figure out when to stop ourselves, mediaSession event?
+
+ // TODO(b/110969180): handle OnTuner Listener
+ @Override
+ public void onTuneFailed(Channel channel) {}
+
+ @Override
+ public void onUnexpectedStop(Channel channel) {}
+
+ @Override
+ public void onStreamInfoChanged(StreamInfo info, boolean allowAutoSelectionOfTrack) {}
+
+ @Override
+ public void onChannelRetuned(Uri channel) {}
+
+ @Override
+ public void onContentBlocked() {}
+
+ @Override
+ public void onContentAllowed() {}
+
+ @Override
+ public void onChannelSignalStrength() {}
+}
diff --git a/src/com/android/tv/audiotvservice/AudioOnlyTvServiceUtil.java b/src/com/android/tv/audiotvservice/AudioOnlyTvServiceUtil.java
new file mode 100644
index 0000000..7ffe883
--- /dev/null
+++ b/src/com/android/tv/audiotvservice/AudioOnlyTvServiceUtil.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.audiotvservice;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+/** Utility methods to start and stop audio only TV Player. */
+public final class AudioOnlyTvServiceUtil {
+ private static final String TAG = "AudioOnlyTvServiceUtil";
+ private static final String EXTRA_INPUT_ID = "intputId";
+
+ @MainThread
+ public static void startAudioOnlyInput(Context context, String tvInputId) {
+ Log.i(TAG, "startAudioOnlyInput");
+ Intent intent = getIntent(context);
+ if (intent == null) {
+ return;
+ }
+ intent.putExtra(EXTRA_INPUT_ID, tvInputId);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent);
+ } else {
+ context.startService(intent);
+ }
+ }
+
+ @Nullable
+ private static Intent getIntent(Context context) {
+ try {
+ return new Intent(
+ context, Class.forName("com.android.tv.audiotvservice.AudioOnlyTvService"));
+ } catch (ClassNotFoundException e) {
+ Log.wtf(TAG, e);
+ return null;
+ }
+ }
+
+ @MainThread
+ public static void stopAudioOnlyInput(Context context) {
+ Log.i(TAG, "stopForegroundService");
+ context.stopService(getIntent(context));
+ }
+
+ @Nullable
+ public static String getInputIdFromIntent(Intent intent) {
+ return intent.getStringExtra(EXTRA_INPUT_ID);
+ }
+
+ private AudioOnlyTvServiceUtil() {}
+}
diff --git a/src/com/android/tv/audiotvservice/README.md b/src/com/android/tv/audiotvservice/README.md
new file mode 100644
index 0000000..0f40ff6
--- /dev/null
+++ b/src/com/android/tv/audiotvservice/README.md
@@ -0,0 +1,18 @@
+# AudioOnlyTvServiceUtil
+
+This service plays audio only TV inputs in the "background".
+
+
+
+## Usage
+
+To start playing call
+
+```java
+AudioOnlyTvServiceUtil.startAudioOnlyInput(context, tivInputServiceUri);
+```
+To stop the playback call.
+
+```java
+AudioOnlyTvServiceUtil.stopAudioOnlyInput(context);
+```
\ No newline at end of file
diff --git a/src/com/android/tv/data/BaseProgram.java b/src/com/android/tv/data/BaseProgram.java
index 0fb1e58..9650fd1 100644
--- a/src/com/android/tv/data/BaseProgram.java
+++ b/src/com/android/tv/data/BaseProgram.java
@@ -21,7 +21,9 @@
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
@@ -43,6 +45,10 @@
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;
@@ -66,7 +72,7 @@
/** Compares two strings represent season numbers or episode numbers of programs. */
public static int numberCompare(String s1, String s2) {
- if (s1 == s2) {
+ if (Objects.equals(s1, s2)) {
return 0;
} else if (s1 == null) {
return -1;
@@ -92,6 +98,7 @@
public abstract String getEpisodeTitle();
/** Returns the displayed title of the program episode. */
+ @Nullable
public String getEpisodeDisplayTitle(Context context) {
String episodeNumber = getEpisodeNumber();
String episodeTitle = getEpisodeTitle();
@@ -162,6 +169,7 @@
public abstract long getDurationMillis();
/** Returns the series ID. */
+ @Nullable
public abstract String getSeriesId();
/** Returns the season number. */
@@ -180,8 +188,7 @@
public abstract int[] getCanonicalGenreIds();
/** Returns the array of content ratings. */
- @Nullable
- public abstract TvContentRating[] getContentRatings();
+ public abstract ImmutableList<TvContentRating> getContentRatings();
/** Returns channel's ID of the program. */
public abstract long getChannelId();
diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java
index 1dfcf12..a5c786c 100644
--- a/src/com/android/tv/data/ChannelDataManager.java
+++ b/src/com/android/tv/data/ChannelDataManager.java
@@ -23,7 +23,6 @@
import android.content.SharedPreferences.Editor;
import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
-import android.database.sqlite.SQLiteException;
import android.media.tv.TvContract;
import android.media.tv.TvContract.Channels;
import android.media.tv.TvInputManager.TvInputCallback;
@@ -47,7 +46,7 @@
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-import java.io.IOException;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -515,7 +514,7 @@
if (mChannelsUpdateTask != null) {
mChannelsUpdateTask.cancel(true);
}
- mChannelsUpdateTask = new QueryAllChannelsTask(mContentResolver);
+ mChannelsUpdateTask = new QueryAllChannelsTask();
mChannelsUpdateTask.executeOnDbThread();
}
@@ -599,8 +598,10 @@
.openAssetFileDescriptor(
TvContract.buildChannelLogoUri(mChannel.getId()), "r")) {
return true;
- } catch (SQLiteException | IOException | NullPointerException e) {
- // File not found or asset file not found.
+ } catch (FileNotFoundException e) {
+ // no need to log just return false
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to find logo for " + mChannel, e);
}
return false;
}
@@ -616,8 +617,8 @@
private final class QueryAllChannelsTask extends AsyncDbTask.AsyncChannelQueryTask {
- QueryAllChannelsTask(ContentResolver contentResolver) {
- super(mDbExecutor, contentResolver);
+ QueryAllChannelsTask() {
+ super(mDbExecutor, mContext);
}
@Override
@@ -736,15 +737,12 @@
return;
}
mDbExecutor.execute(
- new Runnable() {
- @Override
- public void run() {
- String selection = Utils.buildSelectionForIds(Channels._ID, ids);
- ContentValues values = new ContentValues();
- values.put(columnName, columnValue);
- mContentResolver.update(
- TvContract.Channels.CONTENT_URI, values, selection, null);
- }
+ () -> {
+ String selection = Utils.buildSelectionForIds(Channels._ID, ids);
+ ContentValues values = new ContentValues();
+ values.put(columnName, columnValue);
+ mContentResolver.update(
+ TvContract.Channels.CONTENT_URI, values, selection, null);
});
}
diff --git a/src/com/android/tv/data/ChannelImpl.java b/src/com/android/tv/data/ChannelImpl.java
index 703f69c..f31290d 100644
--- a/src/com/android/tv/data/ChannelImpl.java
+++ b/src/com/android/tv/data/ChannelImpl.java
@@ -46,12 +46,8 @@
/** Compares the channel numbers of channels which belong to the same input. */
public static final Comparator<Channel> CHANNEL_NUMBER_COMPARATOR =
- new Comparator<Channel>() {
- @Override
- public int compare(Channel lhs, Channel rhs) {
- return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
- }
- };
+ (Channel lhs, Channel rhs) ->
+ ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
private static final int APP_LINK_TYPE_NOT_SET = 0;
private static final String INVALID_PACKAGE_NAME = "packageName";
@@ -74,6 +70,7 @@
TvContract.Channels.COLUMN_APP_LINK_ICON_URI,
TvContract.Channels.COLUMN_APP_LINK_POSTER_ART_URI,
TvContract.Channels.COLUMN_APP_LINK_INTENT_URI,
+ TvContract.Channels.COLUMN_NETWORK_AFFILIATION,
TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, // Only used in bundled input
};
@@ -102,6 +99,7 @@
channel.mAppLinkIconUri = cursor.getString(index++);
channel.mAppLinkPosterArtUri = cursor.getString(index++);
channel.mAppLinkIntentUri = cursor.getString(index++);
+ channel.mNetworkAffiliation = cursor.getString(index++);
if (CommonUtils.isBundledInput(channel.mInputId)) {
channel.mRecordingProhibited = cursor.getInt(index++) != 0;
}
@@ -146,6 +144,7 @@
private String mAppLinkPosterArtUri;
private String mAppLinkIntentUri;
private Intent mAppLinkIntent;
+ private String mNetworkAffiliation;
private int mAppLinkType;
private String mLogoUri;
private boolean mRecordingProhibited;
@@ -247,6 +246,11 @@
return mAppLinkIntentUri;
}
+ @Override
+ public String getNetworkAffiliation() {
+ return mNetworkAffiliation;
+ }
+
/** Returns channel logo uri which is got from cloud, it's used only for ChannelLogoFetcher. */
@Override
public String getLogoUri() {
@@ -311,6 +315,11 @@
mLogoUri = logoUri;
}
+ @Override
+ public void setNetworkAffiliation(String networkAffiliation) {
+ mNetworkAffiliation = networkAffiliation;
+ }
+
/**
* Check whether {@code other} has same read-only channel info as this. But, it cannot check two
* channels have same logos. It also excludes browsable and locked, because two fields are
@@ -393,8 +402,10 @@
mAppLinkIconUri = channel.getAppLinkIconUri();
mAppLinkPosterArtUri = channel.getAppLinkPosterArtUri();
mAppLinkIntentUri = channel.getAppLinkIntentUri();
+ mNetworkAffiliation = channel.getNetworkAffiliation();
mRecordingProhibited = channel.isRecordingProhibited();
mChannelLogoExist = channel.channelLogoExists();
+ mNetworkAffiliation = channel.getNetworkAffiliation();
}
}
@@ -421,6 +432,7 @@
mAppLinkIconUri = other.mAppLinkIconUri;
mAppLinkPosterArtUri = other.mAppLinkPosterArtUri;
mAppLinkIntentUri = other.mAppLinkIntentUri;
+ mNetworkAffiliation = channel.mNetworkAffiliation;
mAppLinkIntent = other.mAppLinkIntent;
mAppLinkType = other.mAppLinkType;
mRecordingProhibited = other.mRecordingProhibited;
@@ -543,6 +555,12 @@
return this;
}
+ @VisibleForTesting
+ public Builder setNetworkAffiliation(String networkAffiliation) {
+ mChannel.mNetworkAffiliation = networkAffiliation;
+ return this;
+ }
+
public Builder setAppLinkColor(int appLinkColor) {
mChannel.mAppLinkColor = appLinkColor;
return this;
diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java
index 44664dc..8616aee 100644
--- a/src/com/android/tv/data/PreviewDataManager.java
+++ b/src/com/android/tv/data/PreviewDataManager.java
@@ -21,7 +21,6 @@
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
-import android.database.SQLException;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -31,10 +30,10 @@
import android.os.Build;
import android.support.annotation.IntDef;
import android.support.annotation.MainThread;
-import android.support.media.tv.ChannelLogoUtils;
-import android.support.media.tv.PreviewProgram;
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 java.lang.annotation.Retention;
@@ -225,14 +224,14 @@
try (Cursor cursor =
mContentResolver.query(
previewChannelsUri,
- android.support.media.tv.Channel.PROJECTION,
+ androidx.tvprovider.media.tv.Channel.PROJECTION,
mChannelSelection,
new String[] {packageName},
null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
- android.support.media.tv.Channel previewChannel =
- android.support.media.tv.Channel.fromCursor(cursor);
+ androidx.tvprovider.media.tv.Channel previewChannel =
+ androidx.tvprovider.media.tv.Channel.fromCursor(cursor);
Long previewChannelType = previewChannel.getInternalProviderFlag1();
if (previewChannelType != null) {
previewData.addPreviewChannelId(
@@ -245,14 +244,14 @@
try (Cursor cursor =
mContentResolver.query(
previewChannelsUri,
- android.support.media.tv.Channel.PROJECTION,
+ androidx.tvprovider.media.tv.Channel.PROJECTION,
null,
null,
null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
- android.support.media.tv.Channel previewChannel =
- android.support.media.tv.Channel.fromCursor(cursor);
+ androidx.tvprovider.media.tv.Channel previewChannel =
+ androidx.tvprovider.media.tv.Channel.fromCursor(cursor);
Long previewChannelType = previewChannel.getInternalProviderFlag1();
if (packageName.equals(previewChannel.getPackageName())
&& previewChannelType != null) {
@@ -283,7 +282,7 @@
}
}
}
- } catch (SQLException e) {
+ } catch (Exception e) {
Log.w(TAG, "Unable to get preview data", e);
}
return previewData;
@@ -554,7 +553,7 @@
/** A utils class for preview data. */
public static final class PreviewDataUtils {
/** Creates a preview channel. */
- public static android.support.media.tv.Channel createPreviewChannel(
+ public static androidx.tvprovider.media.tv.Channel createPreviewChannel(
Context context, @PreviewChannelType long previewChannelType) {
if (previewChannelType == TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL) {
return createRecordedProgramPreviewChannel(context, previewChannelType);
@@ -562,10 +561,10 @@
return createDefaultPreviewChannel(context, previewChannelType);
}
- private static android.support.media.tv.Channel createDefaultPreviewChannel(
+ private static androidx.tvprovider.media.tv.Channel createDefaultPreviewChannel(
Context context, @PreviewChannelType long previewChannelType) {
- android.support.media.tv.Channel.Builder builder =
- new android.support.media.tv.Channel.Builder();
+ androidx.tvprovider.media.tv.Channel.Builder builder =
+ new androidx.tvprovider.media.tv.Channel.Builder();
CharSequence appLabel =
context.getApplicationInfo().loadLabel(context.getPackageManager());
CharSequence appDescription =
@@ -578,10 +577,10 @@
return builder.build();
}
- private static android.support.media.tv.Channel createRecordedProgramPreviewChannel(
+ private static androidx.tvprovider.media.tv.Channel createRecordedProgramPreviewChannel(
Context context, @PreviewChannelType long previewChannelType) {
- android.support.media.tv.Channel.Builder builder =
- new android.support.media.tv.Channel.Builder();
+ androidx.tvprovider.media.tv.Channel.Builder builder =
+ new androidx.tvprovider.media.tv.Channel.Builder();
builder.setType(TvContract.Channels.TYPE_PREVIEW)
.setDisplayName(
context.getResources()
diff --git a/src/com/android/tv/data/PreviewProgramContent.java b/src/com/android/tv/data/PreviewProgramContent.java
index b515640..8d4b88c 100644
--- a/src/com/android/tv/data/PreviewProgramContent.java
+++ b/src/com/android/tv/data/PreviewProgramContent.java
@@ -19,9 +19,9 @@
import android.content.Context;
import android.net.Uri;
import android.support.annotation.VisibleForTesting;
-import android.support.media.tv.TvContractCompat;
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.dvr.data.RecordedProgram;
diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java
index 2c64cdb..b688927 100644
--- a/src/com/android/tv/data/Program.java
+++ b/src/com/android/tv/data/Program.java
@@ -30,6 +30,7 @@
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
import com.android.tv.common.BuildConfig;
@@ -37,8 +38,10 @@
import com.android.tv.common.util.CollectionUtils;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.api.Channel;
+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;
@@ -86,6 +89,16 @@
public static final String[] PROJECTION = createProjection();
+ public static final String[] PARTIAL_PROJECTION = {
+ TvContract.Programs._ID,
+ TvContract.Programs.COLUMN_CHANNEL_ID,
+ TvContract.Programs.COLUMN_TITLE,
+ TvContract.Programs.COLUMN_EPISODE_TITLE,
+ TvContract.Programs.COLUMN_CANONICAL_GENRE,
+ TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
+ TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
+ };
+
private static String[] createProjection() {
return CollectionUtils.concatAll(
PROJECTION_BASE,
@@ -94,7 +107,10 @@
: PROJECTION_DEPRECATED_IN_NYC);
}
- /** Returns the column index for {@code column}, -1 if the column doesn't exist. */
+ /**
+ * Returns the column index for {@code column},-1 if the column doesn't exist in {@link
+ * #PROJECTION}.
+ */
public static int getColumnIndex(String column) {
for (int i = 0; i < PROJECTION.length; ++i) {
if (PROJECTION[i].equals(column)) {
@@ -104,11 +120,7 @@
return -1;
}
- /**
- * Creates {@code Program} object from cursor.
- *
- * <p>The query that created the cursor MUST use {@link #PROJECTION}.
- */
+ /** Creates {@code Program} object from cursor. */
public static Program fromCursor(Cursor cursor) {
// Columns read must match the order of match {@link #PROJECTION}
Builder builder = new Builder();
@@ -143,6 +155,27 @@
builder.setSeasonNumber(cursor.getString(index++));
builder.setEpisodeNumber(cursor.getString(index++));
}
+ if (TvProviderUtils.getProgramHasSeriesIdColumn()) {
+ String seriesId = cursor.getString(index);
+ if (!TextUtils.isEmpty(seriesId)) {
+ builder.setSeriesId(seriesId);
+ }
+ }
+ return builder.build();
+ }
+
+ /** Creates {@code Program} object from cursor. */
+ public static Program fromCursorPartialProjection(Cursor cursor) {
+ // Columns read must match the order of match {@link #PARTIAL_PROJECTION}
+ Builder builder = new Builder();
+ int index = 0;
+ builder.setId(cursor.getLong(index++));
+ builder.setChannelId(cursor.getLong(index++));
+ builder.setTitle(cursor.getString(index++));
+ builder.setEpisodeTitle(cursor.getString(index++));
+ builder.setCanonicalGenres(cursor.getString(index++));
+ builder.setStartTimeUtcMillis(cursor.getLong(index++));
+ builder.setEndTimeUtcMillis(cursor.getLong(index++));
return builder.build();
}
@@ -169,10 +202,14 @@
program.mCanonicalGenreIds = in.createIntArray();
int length = in.readInt();
if (length > 0) {
- program.mContentRatings = new TvContentRating[length];
+ ImmutableList.Builder<TvContentRating> ratingsBuilder =
+ ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; ++i) {
- program.mContentRatings[i] = TvContentRating.unflattenFromString(in.readString());
+ ratingsBuilder.add(TvContentRating.unflattenFromString(in.readString()));
}
+ program.mContentRatings = ratingsBuilder.build();
+ } else {
+ program.mContentRatings = ImmutableList.of();
}
program.mRecordingProhibited = in.readByte() != (byte) 0;
return program;
@@ -202,6 +239,7 @@
private String mEpisodeNumber;
private long mStartTimeUtcMillis;
private long mEndTimeUtcMillis;
+ private String mDurationString;
private String mDescription;
private String mLongDescription;
private int mVideoWidth;
@@ -210,7 +248,7 @@
private String mPosterArtUri;
private String mThumbnailUri;
private int[] mCanonicalGenreIds;
- private TvContentRating[] mContentRatings;
+ private ImmutableList<TvContentRating> mContentRatings;
private boolean mRecordingProhibited;
private Program() {
@@ -278,6 +316,15 @@
return mEndTimeUtcMillis;
}
+ public String getDurationString(Context context) {
+ // TODO(b/71717446): expire the calculated string
+ if (mDurationString == null) {
+ mDurationString =
+ Utils.getDurationString(context, mStartTimeUtcMillis, mEndTimeUtcMillis, true);
+ }
+ return mDurationString;
+ }
+
/** Returns the program duration. */
@Override
public long getDurationMillis() {
@@ -310,7 +357,7 @@
@Nullable
@Override
- public TvContentRating[] getContentRatings() {
+ public ImmutableList<TvContentRating> getContentRatings() {
return mContentRatings;
}
@@ -379,7 +426,7 @@
mVideoHeight,
mPosterArtUri,
mThumbnailUri,
- Arrays.hashCode(mContentRatings),
+ mContentRatings,
Arrays.hashCode(mCanonicalGenreIds),
mSeasonNumber,
mSeasonTitle,
@@ -407,7 +454,7 @@
&& mVideoHeight == program.mVideoHeight
&& Objects.equals(mPosterArtUri, program.mPosterArtUri)
&& Objects.equals(mThumbnailUri, program.mThumbnailUri)
- && Arrays.equals(mContentRatings, program.mContentRatings)
+ && Objects.equals(mContentRatings, program.mContentRatings)
&& Arrays.equals(mCanonicalGenreIds, program.mCanonicalGenreIds)
&& Objects.equals(mSeasonNumber, program.mSeasonNumber)
&& Objects.equals(mSeasonTitle, program.mSeasonTitle)
@@ -474,7 +521,8 @@
*/
@SuppressLint("InlinedApi")
@SuppressWarnings("deprecation")
- public static ContentValues toContentValues(Program program) {
+ @WorkerThread
+ public static ContentValues toContentValues(Program program, Context context) {
ContentValues values = new ContentValues();
values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId());
if (!TextUtils.isEmpty(program.getPackageName())) {
@@ -495,6 +543,10 @@
putValue(values, TvContract.Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber());
putValue(values, TvContract.Programs.COLUMN_EPISODE_NUMBER, program.getEpisodeNumber());
}
+ if (TvProviderUtils.checkSeriesIdColumn(context, Programs.CONTENT_URI)) {
+ putValue(values, COLUMN_SERIES_ID, program.getSeriesId());
+ }
+
putValue(values, TvContract.Programs.COLUMN_SHORT_DESCRIPTION, program.getDescription());
putValue(values, TvContract.Programs.COLUMN_LONG_DESCRIPTION, program.getLongDescription());
putValue(values, TvContract.Programs.COLUMN_POSTER_ART_URI, program.getPosterArtUri());
@@ -554,6 +606,7 @@
mEpisodeNumber = other.mEpisodeNumber;
mStartTimeUtcMillis = other.mStartTimeUtcMillis;
mEndTimeUtcMillis = other.mEndTimeUtcMillis;
+ mDurationString = null; // Recreate Duration when needed.
mDescription = other.mDescription;
mLongDescription = other.mLongDescription;
mVideoWidth = other.mVideoWidth;
@@ -582,6 +635,7 @@
mProgram.mEpisodeNumber = null;
mProgram.mStartTimeUtcMillis = -1;
mProgram.mEndTimeUtcMillis = -1;
+ mProgram.mDurationString = null;
mProgram.mDescription = null;
mProgram.mLongDescription = null;
mProgram.mRecordingProhibited = false;
@@ -771,7 +825,7 @@
* @param contentRatings the content ratings
* @return a reference to this object
*/
- public Builder setContentRatings(TvContentRating[] contentRatings) {
+ public Builder setContentRatings(ImmutableList<TvContentRating> contentRatings) {
mProgram.mContentRatings = contentRatings;
return this;
}
@@ -947,7 +1001,7 @@
out.writeString(mPosterArtUri);
out.writeString(mThumbnailUri);
out.writeIntArray(mCanonicalGenreIds);
- out.writeInt(mContentRatings == null ? 0 : mContentRatings.length);
+ out.writeInt(mContentRatings == null ? 0 : mContentRatings.size());
if (mContentRatings != null) {
for (TvContentRating rating : mContentRatings) {
out.writeString(rating.flattenToString());
diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java
index 4631806..2f20c89 100644
--- a/src/com/android/tv/data/ProgramDataManager.java
+++ b/src/com/android/tv/data/ProgramDataManager.java
@@ -35,14 +35,17 @@
import android.util.LruCache;
import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.config.api.RemoteConfig;
-import com.android.tv.common.config.api.RemoteConfigValue;
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.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.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;
@@ -71,8 +74,6 @@
// TODO: need to optimize consecutive DB updates.
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);
- private static final RemoteConfigValue<Long> PROGRAM_GUIDE_MAX_HOURS =
- RemoteConfigValue.create("live_channels_program_guide_max_hours", 48);
// TODO: Use TvContract constants, once they become public.
private static final String PARAM_START_TIME = "start_time";
@@ -90,10 +91,13 @@
private static final int MSG_UPDATE_ONE_CURRENT_PROGRAM = 1001;
private static final int MSG_UPDATE_PREFETCH_PROGRAM = 1002;
+ private final Context mContext;
private final Clock mClock;
private final ContentResolver mContentResolver;
private final Executor mDbExecutor;
- private final RemoteConfig mRemoteConfig;
+ private final BackendKnobsFlags mBackendKnobsFlags;
+ private final PerformanceMonitor mPerformanceMonitor;
+ private final ChannelDataManager mChannelDataManager;
private boolean mStarted;
// Updated only on the main thread.
private volatile boolean mCurrentProgramsLoadFinished;
@@ -104,15 +108,15 @@
private final MultiLongSparseArray<OnCurrentProgramUpdatedListener>
mChannelId2ProgramUpdatedListeners = new MultiLongSparseArray<>();
private final Handler mHandler;
- private final Set<Listener> mListeners = new ArraySet<>();
-
+ private final Set<Callback> mCallbacks = new ArraySet<>();
+ private Map<Long, ArrayList<Program>> mChannelIdProgramCache = new ConcurrentHashMap<>();
+ private final Set<Long> mCompleteInfoChannelIds = new HashSet<>();
private final ContentObserver mProgramObserver;
private boolean mPrefetchEnabled;
private long mProgramPrefetchUpdateWaitMs;
private long mLastPrefetchTaskRunMs;
private ProgramsPrefetchTask mProgramsPrefetchTask;
- private Map<Long, ArrayList<Program>> mChannelIdProgramCache = new HashMap<>();
// Any program that ends prior to this time will be removed from the cache
// when a channel's current program is updated.
@@ -125,25 +129,34 @@
@MainThread
public ProgramDataManager(Context context) {
this(
+ context,
TvSingletons.getSingletons(context).getDbExecutor(),
context.getContentResolver(),
Clock.SYSTEM,
Looper.myLooper(),
- TvSingletons.getSingletons(context).getRemoteConfig());
+ TvSingletons.getSingletons(context).getBackendKnobs(),
+ TvSingletons.getSingletons(context).getPerformanceMonitor(),
+ TvSingletons.getSingletons(context).getChannelDataManager());
}
@VisibleForTesting
ProgramDataManager(
+ Context context,
Executor executor,
ContentResolver contentResolver,
Clock time,
Looper looper,
- RemoteConfig remoteConfig) {
+ BackendKnobsFlags backendKnobsFlags,
+ PerformanceMonitor performanceMonitor,
+ ChannelDataManager channelDataManager) {
+ mContext = context;
mDbExecutor = executor;
mClock = time;
mContentResolver = contentResolver;
mHandler = new MyHandler(looper);
- mRemoteConfig = remoteConfig;
+ mBackendKnobsFlags = backendKnobsFlags;
+ mPerformanceMonitor = performanceMonitor;
+ mChannelDataManager = channelDataManager;
mProgramObserver =
new ContentObserver(mHandler) {
@Override
@@ -246,24 +259,43 @@
}
}
- /** A listener interface to receive notification on program data retrieval from DB. */
- public interface Listener {
+ 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());
+ new SingleChannelPrefetchTask(channelId, startTimeMs, endTimeMs).executeOnDbThread();
+ }
+ }
+
+ /** A Callback interface to receive notification on program data retrieval from DB. */
+ public interface Callback {
/**
* Called when a Program data is now available through getProgram() after the DB operation
* is done which wasn't before. This would be called only if fetched data is around the
* selected program.
*/
void onProgramUpdated();
+
+ /**
+ * Called when we update complete program data of specific channel during scrolling. Data is
+ * loaded from DB on request basis.
+ *
+ * @param channelId
+ */
+ void onSingleChannelUpdated(long channelId);
}
- /** Adds the {@link Listener}. */
- public void addListener(Listener listener) {
- mListeners.add(listener);
+ /** Adds the {@link Callback}. */
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
}
- /** Removes the {@link Listener}. */
- public void removeListener(Listener listener) {
- mListeners.remove(listener);
+ /** Removes the {@link Callback}. */
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
}
/** Enables or Disables program prefetch. */
@@ -451,7 +483,7 @@
}
clearTask(mProgramUpdateTaskMap);
mHandler.removeMessages(MSG_UPDATE_ONE_CURRENT_PROGRAM);
- mProgramsUpdateTask = new ProgramsUpdateTask(mContentResolver, mClock.currentTimeMillis());
+ mProgramsUpdateTask = new ProgramsUpdateTask(mClock.currentTimeMillis());
mProgramsUpdateTask.executeOnDbThread();
}
@@ -461,20 +493,29 @@
private final long mEndTimeMs;
private boolean mSuccess;
+ private TimerEvent mFromEmptyCacheTimeEvent;
public ProgramsPrefetchTask() {
super(mDbExecutor);
long time = mClock.currentTimeMillis();
mStartTimeMs =
Utils.floorTime(time - PROGRAM_GUIDE_SNAP_TIME_MS, PROGRAM_GUIDE_SNAP_TIME_MS);
- mEndTimeMs =
- mStartTimeMs
- + TimeUnit.HOURS.toMillis(PROGRAM_GUIDE_MAX_HOURS.get(mRemoteConfig));
+ mEndTimeMs = mStartTimeMs + TimeUnit.HOURS.toMillis(getFetchDuration());
mSuccess = false;
}
@Override
+ protected void onPreExecute() {
+ if (mChannelIdCurrentProgramMap.isEmpty()) {
+ // No current program guide is shown.
+ // Measure the delay before users can see program guides.
+ mFromEmptyCacheTimeEvent = mPerformanceMonitor.startTimer();
+ }
+ }
+
+ @Override
protected Map<Long, ArrayList<Program>> doInBackground(Void... params) {
+ TimerEvent asyncTimeEvent = mPerformanceMonitor.startTimer();
Map<Long, ArrayList<Program>> programMap = new HashMap<>();
if (DEBUG) {
Log.d(
@@ -497,8 +538,19 @@
return null;
}
programMap.clear();
- try (Cursor c =
- mContentResolver.query(uri, Program.PROJECTION, null, null, SORT_BY_TIME)) {
+
+ String[] projection =
+ mBackendKnobsFlags.enablePartialProgramFetch()
+ ? Program.PARTIAL_PROJECTION
+ : Program.PROJECTION;
+ if (TvProviderUtils.checkSeriesIdColumn(mContext, Programs.CONTENT_URI)) {
+ if (Utils.isProgramsUri(uri)) {
+ projection =
+ TvProviderUtils.addExtraColumnsToProjection(
+ projection, TvProviderUtils.EXTRA_PROGRAM_COLUMN_SERIES_ID);
+ }
+ }
+ try (Cursor c = mContentResolver.query(uri, projection, null, null, SORT_BY_TIME)) {
if (c == null) {
continue;
}
@@ -510,7 +562,10 @@
}
return null;
}
- Program program = Program.fromCursor(c);
+ Program program =
+ mBackendKnobsFlags.enablePartialProgramFetch()
+ ? Program.fromCursorPartialProjection(c)
+ : Program.fromCursor(c);
if (Program.isDuplicate(program, lastReadProgram)) {
duplicateCount++;
continue;
@@ -520,6 +575,15 @@
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;
+ }
+ }
programMap.put(program.getChannelId(), programs);
}
programs.add(program);
@@ -534,12 +598,17 @@
Log.d(TAG, "Database is changed while querying. Will retry.");
}
} catch (SecurityException e) {
- Log.d(TAG, "Security exception during program data query", e);
+ Log.w(TAG, "Security exception during program data query", e);
+ } catch (Exception e) {
+ Log.w(TAG, "Error during program data query", e);
}
}
if (DEBUG) {
Log.d(TAG, "Ends programs prefetch for " + programMap.size() + " channels");
}
+ mPerformanceMonitor.stopTimer(
+ asyncTimeEvent,
+ EventNames.PROGRAM_DATA_MANAGER_PROGRAMS_PREFETCH_TASK_DO_IN_BACKGROUND);
return programMap;
}
@@ -552,8 +621,6 @@
}
long nextMessageDelayedTime;
if (mSuccess) {
- mChannelIdProgramCache = programs;
- notifyProgramUpdated();
long currentTime = mClock.currentTimeMillis();
mLastPrefetchTaskRunMs = currentTime;
nextMessageDelayedTime =
@@ -561,6 +628,22 @@
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();
+ }
+ notifyProgramUpdated();
+ if (mFromEmptyCacheTimeEvent != null) {
+ mPerformanceMonitor.stopTimer(
+ mFromEmptyCacheTimeEvent,
+ EventNames.PROGRAM_GUIDE_SHOW_FROM_EMPTY_CACHE);
+ mFromEmptyCacheTimeEvent = null;
+ }
} else {
nextMessageDelayedTime = PERIODIC_PROGRAM_UPDATE_MIN_MS;
}
@@ -571,17 +654,78 @@
}
}
+ private long getFetchDuration() {
+ if (mChannelIdProgramCache.isEmpty()) {
+ return Math.max(1L, mBackendKnobsFlags.programGuideInitialFetchHours());
+ } else {
+ long durationHours;
+ int channelCount = mChannelDataManager.getChannelCount();
+ long knobsMaxHours = mBackendKnobsFlags.programGuideMaxHours();
+ long targetChannelCount = mBackendKnobsFlags.epgTargetChannelCount();
+ if (channelCount <= targetChannelCount) {
+ durationHours = Math.max(48L, knobsMaxHours);
+ } else {
+ // 2 days <= duration <= 14 days (336 hours)
+ durationHours = knobsMaxHours * targetChannelCount / channelCount;
+ if (durationHours < 48L) {
+ durationHours = 48L;
+ } else if (durationHours > 336L) {
+ durationHours = 336L;
+ }
+ }
+ return durationHours;
+ }
+ }
+
+ private class SingleChannelPrefetchTask extends AsyncDbTask.AsyncQueryTask<ArrayList<Program>> {
+ long mChannelId;
+
+ public SingleChannelPrefetchTask(long channelId, long startTimeMs, long endTimeMs) {
+ super(
+ mDbExecutor,
+ mContext,
+ TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs),
+ Program.PROJECTION,
+ null,
+ null,
+ SORT_BY_TIME);
+ mChannelId = channelId;
+ }
+
+ @Override
+ protected ArrayList<Program> onQuery(Cursor c) {
+ ArrayList<Program> programMap = new ArrayList<>();
+ while (c.moveToNext()) {
+ Program program = Program.fromCursor(c);
+ programMap.add(program);
+ }
+ return programMap;
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList<Program> programs) {
+ mChannelIdProgramCache.put(mChannelId, programs);
+ notifySingleChannelUpdated(mChannelId);
+ }
+ }
+
private void notifyProgramUpdated() {
- for (Listener listener : mListeners) {
- listener.onProgramUpdated();
+ for (Callback callback : mCallbacks) {
+ callback.onProgramUpdated();
+ }
+ }
+
+ private void notifySingleChannelUpdated(long channelId) {
+ for (Callback callback : mCallbacks) {
+ callback.onSingleChannelUpdated(channelId);
}
}
private class ProgramsUpdateTask extends AsyncDbTask.AsyncQueryTask<List<Program>> {
- public ProgramsUpdateTask(ContentResolver contentResolver, long time) {
+ public ProgramsUpdateTask(long time) {
super(
mDbExecutor,
- contentResolver,
+ mContext,
Programs.CONTENT_URI
.buildUpon()
.appendQueryParameter(PARAM_START_TIME, String.valueOf(time))
@@ -633,6 +777,9 @@
for (Long channelId : removedChannelIds) {
if (mPrefetchEnabled) {
mChannelIdProgramCache.remove(channelId);
+ if (mBackendKnobsFlags.enablePartialProgramFetch()) {
+ mCompleteInfoChannelIds.remove(channelId);
+ }
}
mChannelIdCurrentProgramMap.remove(channelId);
notifyCurrentProgramUpdate(channelId, null);
@@ -645,11 +792,10 @@
private class UpdateCurrentProgramForChannelTask extends AsyncDbTask.AsyncQueryTask<Program> {
private final long mChannelId;
- private UpdateCurrentProgramForChannelTask(
- ContentResolver contentResolver, long channelId, long time) {
+ private UpdateCurrentProgramForChannelTask(long channelId, long time) {
super(
mDbExecutor,
- contentResolver,
+ mContext,
TvContract.buildProgramsUriForChannel(channelId, time, time),
Program.PROJECTION,
null,
@@ -695,7 +841,7 @@
}
UpdateCurrentProgramForChannelTask task =
new UpdateCurrentProgramForChannelTask(
- mContentResolver, channelId, mClock.currentTimeMillis());
+ channelId, mClock.currentTimeMillis());
mProgramUpdateTaskMap.put(channelId, task);
task.executeOnDbThread();
break;
diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java
index 7187efd..9c1d423 100644
--- a/src/com/android/tv/data/WatchedHistoryManager.java
+++ b/src/com/android/tv/data/WatchedHistoryManager.java
@@ -34,6 +34,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -73,24 +74,20 @@
// onNewRecordAdded will be called in the same thread as the thread
// which created this instance.
mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- for (long i = mLastIndex + 1; i <= lastIndex; ++i) {
- WatchedRecord record =
- decode(
- mSharedPreferences.getString(
- getSharedPreferencesKey(i),
- null));
- if (record != null) {
- mWatchedHistory.add(record);
- if (mListener != null) {
- mListener.onNewRecordAdded(record);
- }
+ () -> {
+ for (long i = mLastIndex + 1; i <= lastIndex; ++i) {
+ WatchedRecord record =
+ decode(
+ mSharedPreferences.getString(
+ getSharedPreferencesKey(i), null));
+ if (record != null) {
+ mWatchedHistory.add(record);
+ if (mListener != null) {
+ mListener.onNewRecordAdded(record);
}
}
- mLastIndex = lastIndex;
}
+ mLastIndex = lastIndex;
});
}
}
@@ -100,16 +97,18 @@
private Listener mListener;
private final int mMaxHistorySize;
private final Handler mHandler;
+ private final Executor mExecutor;
public WatchedHistoryManager(Context context) {
- this(context, MAX_HISTORY_SIZE);
+ this(context, MAX_HISTORY_SIZE, AsyncTask.THREAD_POOL_EXECUTOR);
}
@VisibleForTesting
- WatchedHistoryManager(Context context, int maxHistorySize) {
+ WatchedHistoryManager(Context context, int maxHistorySize, Executor executor) {
mContext = context.getApplicationContext();
mMaxHistorySize = maxHistorySize;
mHandler = new Handler();
+ mExecutor = executor;
}
/** Starts the manager. It loads history data from {@link SharedPreferences}. */
@@ -130,7 +129,7 @@
protected void onPostExecute(Void params) {
onLoadFinished();
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }.executeOnExecutor(mExecutor);
} else {
loadWatchedHistory();
onLoadFinished();
diff --git a/src/com/android/tv/data/api/Channel.java b/src/com/android/tv/data/api/Channel.java
index 496331c..fb00952 100644
--- a/src/com/android/tv/data/api/Channel.java
+++ b/src/com/android/tv/data/api/Channel.java
@@ -85,6 +85,8 @@
String getAppLinkIntentUri();
+ String getNetworkAffiliation();
+
String getLogoUri();
boolean isRecordingProhibited();
@@ -109,6 +111,8 @@
void setLogoUri(String logoUri);
+ void setNetworkAffiliation(String networkAffiliation);
+
boolean channelLogoExists();
void loadBitmap(
diff --git a/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java b/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java
deleted file mode 100644
index 795ad5c..0000000
--- a/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java
+++ /dev/null
@@ -1,86 +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.data.epg;
-
-import com.android.tv.data.api.Channel;
-
-/**
- * Hand copy of generated Autovalue class.
- *
- * TODO get autovalue working
- */
-final class AutoValue_EpgReader_EpgChannel extends EpgReader.EpgChannel {
-
- private final Channel channel;
- private final String epgChannelId;
-
- AutoValue_EpgReader_EpgChannel(
- Channel channel,
- String epgChannelId) {
- if (channel == null) {
- throw new NullPointerException("Null channel");
- }
- this.channel = channel;
- if (epgChannelId == null) {
- throw new NullPointerException("Null epgChannelId");
- }
- this.epgChannelId = epgChannelId;
- }
-
- @Override
- public Channel getChannel() {
- return channel;
- }
-
- @Override
- public String getEpgChannelId() {
- return epgChannelId;
- }
-
- @Override
- public String toString() {
- return "EpgChannel{"
- + "channel=" + channel + ", "
- + "epgChannelId=" + epgChannelId
- + "}";
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o instanceof EpgReader.EpgChannel) {
- EpgReader.EpgChannel that = (EpgReader.EpgChannel) o;
- return (this.channel.equals(that.getChannel()))
- && (this.epgChannelId.equals(that.getEpgChannelId()));
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- int h = 1;
- h *= 1000003;
- h ^= this.channel.hashCode();
- h *= 1000003;
- h ^= this.epgChannelId.hashCode();
- return h;
- }
-
-}
-
diff --git a/src/com/android/tv/data/epg/EpgFetchHelper.java b/src/com/android/tv/data/epg/EpgFetchHelper.java
index 3c7112e..3843ca9 100644
--- a/src/com/android/tv/data/epg/EpgFetchHelper.java
+++ b/src/com/android/tv/data/epg/EpgFetchHelper.java
@@ -17,6 +17,7 @@
package com.android.tv.data.epg;
import android.content.ContentProviderOperation;
+import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
@@ -30,9 +31,13 @@
import com.android.tv.common.CommonConstants;
import com.android.tv.common.util.Clock;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
+import com.android.tv.features.TvFeatures;
+import com.android.tv.util.TvProviderUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/** The helper class for {@link EpgFetcher} */
@@ -101,7 +106,7 @@
ops.add(
ContentProviderOperation.newUpdate(
TvContract.buildProgramUri(oldProgram.getId()))
- .withValues(Program.toContentValues(newProgram))
+ .withValues(Program.toContentValues(newProgram, context))
.build());
oldProgramsIndex++;
newProgramsIndex++;
@@ -127,7 +132,7 @@
if (addNewProgram) {
ops.add(
ContentProviderOperation.newInsert(Programs.CONTENT_URI)
- .withValues(Program.toContentValues(newProgram))
+ .withValues(Program.toContentValues(newProgram, context))
.build());
}
// Throttle the batch operation not to cause TransactionTooLargeException.
@@ -155,14 +160,57 @@
return updated;
}
+ @WorkerThread
+ static void updateNetworkAffiliation(Context context, Set<EpgReader.EpgChannel> channels) {
+ if (!TvFeatures.STORE_NETWORK_AFFILIATION.isEnabled(context)) {
+ return;
+ }
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ for (EpgReader.EpgChannel epgChannel : channels) {
+ if (!epgChannel.getDbUpdateNeeded()) {
+ continue;
+ }
+ Channel channel = epgChannel.getChannel();
+
+ ContentValues values = new ContentValues();
+ values.put(
+ TvContract.Channels.COLUMN_NETWORK_AFFILIATION,
+ channel.getNetworkAffiliation());
+ ops.add(
+ ContentProviderOperation.newUpdate(TvContract.buildChannelUri(channel.getId()))
+ .withValues(values)
+ .build());
+ if (ops.size() >= BATCH_OPERATION_COUNT) {
+ try {
+ context.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
+ } catch (RemoteException | OperationApplicationException e) {
+ Log.e(TAG, "Failed to update channels.", e);
+ }
+ ops.clear();
+ }
+ }
+ try {
+ context.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
+ } catch (RemoteException | OperationApplicationException e) {
+ Log.e(TAG, "Failed to update channels.", e);
+ }
+ }
+
+ @WorkerThread
private static List<Program> queryPrograms(
Context context, long channelId, long startTimeMs, long endTimeMs) {
+ String[] projection = Program.PROJECTION;
+ if (TvProviderUtils.checkSeriesIdColumn(context, Programs.CONTENT_URI)) {
+ projection =
+ TvProviderUtils.addExtraColumnsToProjection(
+ projection, TvProviderUtils.EXTRA_PROGRAM_COLUMN_SERIES_ID);
+ }
try (Cursor c =
context.getContentResolver()
.query(
TvContract.buildProgramsUriForChannel(
channelId, startTimeMs, endTimeMs),
- Program.PROJECTION,
+ projection,
null,
null,
Programs.COLUMN_START_TIME_UTC_MILLIS)) {
diff --git a/src/com/android/tv/data/epg/EpgFetcherImpl.java b/src/com/android/tv/data/epg/EpgFetcherImpl.java
index 2aaaa5b..b191421 100644
--- a/src/com/android/tv/data/epg/EpgFetcherImpl.java
+++ b/src/com/android/tv/data/epg/EpgFetcherImpl.java
@@ -38,11 +38,10 @@
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.TvFeatures;
import com.android.tv.TvSingletons;
import com.android.tv.common.BuildConfig;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.config.api.RemoteConfigValue;
+import com.android.tv.common.buildtype.HasBuildType;
import com.android.tv.common.util.Clock;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.common.util.LocationUtils;
@@ -55,12 +54,15 @@
import com.android.tv.data.Lineup;
import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
+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;
@@ -100,8 +102,7 @@
private static final long FETCH_DURING_SCAN_DURATION_SEC = TimeUnit.HOURS.toSeconds(3);
private static final long FAST_FETCH_DURATION_SEC = TimeUnit.DAYS.toSeconds(2);
- private static final RemoteConfigValue<Long> ROUTINE_INTERVAL_HOUR =
- RemoteConfigValue.create("live_channels_epg_fetcher_interval_hour", 4);
+ private static final long DEFAULT_ROUTINE_INTERVAL_HOUR = 4;
private static final int MSG_PREPARE_FETCH_DURING_SCAN = 1;
private static final int MSG_CHANNEL_UPDATED_DURING_SCAN = 2;
@@ -115,6 +116,9 @@
private final ChannelDataManager mChannelDataManager;
private final EpgReader mEpgReader;
private final PerformanceMonitor mPerformanceMonitor;
+ private final EpgInputWhiteList mEpgInputWhiteList;
+ private final BackendKnobsFlags mBackendKnobsFlags;
+ private final HasBuildType.BuildType mBuildType;
private FetchAsyncTask mFetchTask;
private FetchDuringScanHandler mFetchDuringScanHandler;
private long mEpgTimeStamp;
@@ -124,9 +128,6 @@
// A flag to block the re-entrance of onChannelScanStarted and onChannelScanFinished.
private boolean mScanStarted;
- private final long mRoutineIntervalMs;
- private final long mEpgDataExpiredTimeLimitMs;
- private final long mFastFetchDurationSec;
private Clock mClock;
public static EpgFetcher create(Context context) {
@@ -136,36 +137,54 @@
PerformanceMonitor performanceMonitor = tvSingletons.getPerformanceMonitor();
EpgReader epgReader = tvSingletons.providesEpgReader().get();
Clock clock = tvSingletons.getClock();
- long routineIntervalMs = ROUTINE_INTERVAL_HOUR.get(tvSingletons.getRemoteConfig());
-
+ EpgInputWhiteList epgInputWhiteList =
+ new EpgInputWhiteList(tvSingletons.getCloudEpgFlags());
+ BackendKnobsFlags backendKnobsFlags = tvSingletons.getBackendKnobs();
+ HasBuildType.BuildType buildType = tvSingletons.getBuildType();
return new EpgFetcherImpl(
context,
+ epgInputWhiteList,
channelDataManager,
epgReader,
performanceMonitor,
clock,
- routineIntervalMs);
+ backendKnobsFlags,
+ buildType);
}
@VisibleForTesting
EpgFetcherImpl(
Context context,
+ EpgInputWhiteList epgInputWhiteList,
ChannelDataManager channelDataManager,
EpgReader epgReader,
PerformanceMonitor performanceMonitor,
Clock clock,
- long routineIntervalMs) {
+ BackendKnobsFlags backendKnobsFlags,
+ HasBuildType.BuildType buildType) {
mContext = context;
mChannelDataManager = channelDataManager;
mEpgReader = epgReader;
mPerformanceMonitor = performanceMonitor;
mClock = clock;
- mRoutineIntervalMs =
- routineIntervalMs <= 0
- ? TimeUnit.HOURS.toMillis(ROUTINE_INTERVAL_HOUR.getDefaultValue())
- : TimeUnit.HOURS.toMillis(routineIntervalMs);
- mEpgDataExpiredTimeLimitMs = routineIntervalMs * 2;
- mFastFetchDurationSec = FAST_FETCH_DURATION_SEC + routineIntervalMs / 1000;
+ mEpgInputWhiteList = epgInputWhiteList;
+ mBackendKnobsFlags = backendKnobsFlags;
+ mBuildType = buildType;
+ }
+
+ private long getFastFetchDurationSec() {
+ return FAST_FETCH_DURATION_SEC + getRoutineIntervalMs() / 1000;
+ }
+
+ private long getEpgDataExpiredTimeLimitMs() {
+ return getRoutineIntervalMs() * 2;
+ }
+
+ private long getRoutineIntervalMs() {
+ long routineIntervalHours = mBackendKnobsFlags.epgFetcherIntervalHour();
+ return routineIntervalHours <= 0
+ ? TimeUnit.HOURS.toMillis(DEFAULT_ROUTINE_INTERVAL_HOUR)
+ : TimeUnit.HOURS.toMillis(routineIntervalHours);
}
private static Set<Channel> getExistingChannelsForMyPackage(Context context) {
@@ -214,7 +233,7 @@
new JobInfo.Builder(
EPG_ROUTINELY_FETCHING_JOB_ID,
new ComponentName(mContext, EpgFetchService.class))
- .setPeriodic(mRoutineIntervalMs)
+ .setPeriodic(getRoutineIntervalMs())
.setBackoffCriteria(INITIAL_BACKOFF_MS, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
.setPersisted(true)
.build();
@@ -238,7 +257,7 @@
@Override
protected void onPostExecute(Long result) {
if (mClock.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
- > mEpgDataExpiredTimeLimitMs) {
+ > getEpgDataExpiredTimeLimitMs()) {
Log.i(TAG, "EPG data expired. Start fetching immediately.");
fetchImmediately();
}
@@ -346,6 +365,19 @@
if (DEBUG) Log.d(TAG, "Cannot start routine service: scanning channels.");
return false;
}
+ if (!TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.isEnabled(mContext)
+ && mBuildType != HasBuildType.BuildType.AOSP) {
+ if (getTunerChannelCount() == 0) {
+ if (DEBUG) Log.d(TAG, "Cannot start routine service: no internal tuner channels.");
+ return false;
+ }
+ if (!TextUtils.isEmpty(EpgFetchHelper.getLastLineupId(mContext))) {
+ return true;
+ }
+ if (!TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
+ return true;
+ }
+ }
return true;
}
@@ -505,6 +537,17 @@
return numbers.size();
}
+ private boolean isInputInWhiteList(EpgInput epgInput) {
+ if (mBuildType == HasBuildType.BuildType.AOSP) {
+ return false;
+ }
+ return (BuildConfig.ENG
+ && epgInput.getInputId()
+ .equals(
+ "com.example.partnersupportsampletvinput/.SampleTvInputService"))
+ || mEpgInputWhiteList.isInputWhiteListed(epgInput.getInputId());
+ }
+
@VisibleForTesting
class FetchAsyncTask extends AsyncTask<Void, Void, Integer> {
private final JobService mService;
@@ -532,12 +575,45 @@
Integer builtInResult = fetchEpgForBuiltInTuner();
boolean anyCloudEpgFailure = false;
boolean anyCloudEpgSuccess = false;
+ if (TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.isEnabled(mContext)
+ && mBuildType != HasBuildType.BuildType.AOSP) {
+ for (EpgInput epgInput : getEpgInputs()) {
+ if (DEBUG) Log.d(TAG, "Start EPG fetch for " + epgInput);
+ if (isCancelled()) {
+ break;
+ }
+ if (isInputInWhiteList(epgInput)) {
+ // TODO(b/66191312) check timestamp and result code and decide if update
+ // is needed.
+ Set<Channel> channels = getExistingChannelsFor(epgInput.getInputId());
+ Integer result = fetchEpgFor(epgInput.getLineupId(), channels);
+ anyCloudEpgFailure = anyCloudEpgFailure || result != null;
+ anyCloudEpgSuccess = anyCloudEpgSuccess || result == null;
+ updateCloudEpgInput(epgInput, result);
+ } else {
+ Log.w(
+ TAG,
+ "Fetching the EPG for "
+ + epgInput.getInputId()
+ + " is not supported.");
+ }
+ }
+ }
+ if (builtInResult == null || builtInResult == REASON_NO_BUILT_IN_CHANNELS) {
+ return anyCloudEpgFailure
+ ? ((Integer) REASON_CLOUD_EPG_FAILURE)
+ : anyCloudEpgSuccess ? null : builtInResult;
+ }
return builtInResult;
} finally {
TrafficStats.setThreadStatsTag(oldTag);
}
}
+ private void updateCloudEpgInput(EpgInput unusedEpgInput, Integer unusedResult) {
+ // TODO(b/66191312) write the result and timestamp to the input table
+ }
+
private Set<Channel> getExistingChannelsFor(String inputId) {
Set<Channel> result = new HashSet<>();
try (Cursor cursor =
@@ -548,13 +624,24 @@
null,
null,
null)) {
- while (cursor.moveToNext()) {
- result.add(ChannelImpl.fromCursor(cursor));
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ result.add(ChannelImpl.fromCursor(cursor));
+ }
}
return result;
}
}
+ private Set<EpgInput> getEpgInputs() {
+ if (mBuildType == HasBuildType.BuildType.AOSP) {
+ return ImmutableSet.of();
+ }
+ Set<EpgInput> epgInputs = EpgInputs.queryEpgInputs(mContext.getContentResolver());
+ if (DEBUG) Log.d(TAG, "getEpgInputs " + epgInputs);
+ return epgInputs;
+ }
+
private Integer fetchEpgForBuiltInTuner() {
try {
Integer failureReason = prepareFetchEpg(false);
@@ -606,19 +693,16 @@
Log.i(TAG, "Failed to get EPG channels for " + lineupId);
return REASON_NO_EPG_DATA_RETURNED;
}
+ EpgFetchHelper.updateNetworkAffiliation(mContext, channels);
if (mClock.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
- > mEpgDataExpiredTimeLimitMs) {
- batchFetchEpg(channels, mFastFetchDurationSec);
+ > getEpgDataExpiredTimeLimitMs()) {
+ batchFetchEpg(channels, getFastFetchDurationSec());
}
new Handler(mContext.getMainLooper())
.post(
- new Runnable() {
- @Override
- public void run() {
+ () ->
ChannelLogoFetcher.startFetchingChannelLogos(
- mContext, asChannelList(channels));
- }
- });
+ mContext, asChannelList(channels)));
for (EpgReader.EpgChannel epgChannel : channels) {
if (this.isCancelled()) {
return null;
@@ -780,6 +864,9 @@
mFetchedChannelIdsDuringScan.add(epgChannel.getChannel().getId());
}
}
+ if (!newChannels.isEmpty()) {
+ EpgFetchHelper.updateNetworkAffiliation(mContext, newChannels);
+ }
batchFetchEpg(newChannels, FETCH_DURING_SCAN_DURATION_SEC);
}
@@ -798,14 +885,7 @@
// Clear timestamp to make routine service start right away.
EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0);
Log.i(TAG, "EPG Fetching during channel scanning finished.");
- new Handler(Looper.getMainLooper())
- .post(
- new Runnable() {
- @Override
- public void run() {
- fetchImmediately();
- }
- });
+ new Handler(Looper.getMainLooper()).post(EpgFetcherImpl.this::fetchImmediately);
}
}
}
diff --git a/src/com/android/tv/data/epg/EpgInputWhiteList.java b/src/com/android/tv/data/epg/EpgInputWhiteList.java
index eada8b2..24b4fe3 100644
--- a/src/com/android/tv/data/epg/EpgInputWhiteList.java
+++ b/src/com/android/tv/data/epg/EpgInputWhiteList.java
@@ -21,8 +21,8 @@
import android.text.TextUtils;
import android.util.Log;
import com.android.tv.common.BuildConfig;
-import com.android.tv.common.config.api.RemoteConfig;
import com.android.tv.common.experiments.Experiments;
+import com.android.tv.common.flags.CloudEpgFlags;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -33,7 +33,6 @@
public final class EpgInputWhiteList {
private static final boolean DEBUG = false;
private static final String TAG = "EpgInputWhiteList";
- @VisibleForTesting public static final String KEY = "live_channels_3rd_party_epg_inputs";
private static final String QA_DEV_INPUTS =
"com.example.partnersupportsampletvinput/.SampleTvInputService,"
+ "com.android.tv.tuner.sample.dvb/.tvinput.SampleDvbTunerTvInputService";
@@ -44,10 +43,10 @@
return inputId == null ? null : inputId.substring(0, inputId.indexOf("/"));
}
- private final RemoteConfig remoteConfig;
+ private final CloudEpgFlags cloudEpgFlags;
- public EpgInputWhiteList(RemoteConfig remoteConfig) {
- this.remoteConfig = remoteConfig;
+ public EpgInputWhiteList(CloudEpgFlags cloudEpgFlags) {
+ this.cloudEpgFlags = cloudEpgFlags;
}
public boolean isInputWhiteListed(String inputId) {
@@ -72,7 +71,7 @@
}
private Set<String> getWhiteListedInputs() {
- Set<String> result = toInputSet(remoteConfig.getString(KEY));
+ Set<String> result = toInputSet(cloudEpgFlags.thirdPartyEpgInputsCsv());
if (BuildConfig.ENG || Experiments.ENABLE_QA_FEATURES.get()) {
HashSet<String> moreInputs = new HashSet<>(toInputSet(QA_DEV_INPUTS));
if (result.isEmpty()) {
diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java
index 7147905..c9fcd97 100644
--- a/src/com/android/tv/data/epg/EpgReader.java
+++ b/src/com/android/tv/data/epg/EpgReader.java
@@ -23,6 +23,7 @@
import com.android.tv.data.Program;
import com.android.tv.data.api.Channel;
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;
@@ -33,15 +34,18 @@
public interface EpgReader {
/** Value class that holds a EpgChannelId and its corresponding {@link Channel} */
- // TODO(b/72052568): Get autovalue to work in aosp master
+ @AutoValue
abstract class EpgChannel {
- public static EpgChannel createEpgChannel(Channel channel, String epgChannelId) {
- return new AutoValue_EpgReader_EpgChannel(channel, epgChannelId);
+ public static EpgChannel createEpgChannel(Channel channel, String epgChannelId,
+ boolean dbUpdateNeeded) {
+ return new AutoValue_EpgReader_EpgChannel(channel, epgChannelId, dbUpdateNeeded);
}
public abstract Channel getChannel();
public abstract String getEpgChannelId();
+
+ public abstract boolean getDbUpdateNeeded();
}
/** Checks if the reader is available. */
diff --git a/src/com/android/tv/dialog/PinDialogFragment.java b/src/com/android/tv/dialog/PinDialogFragment.java
index 71f45fb..8730809 100644
--- a/src/com/android/tv/dialog/PinDialogFragment.java
+++ b/src/com/android/tv/dialog/PinDialogFragment.java
@@ -16,37 +16,26 @@
package com.android.tv.dialog;
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.Dialog;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
-import android.content.res.Resources;
import android.media.tv.TvContentRating;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.TextUtils;
-import android.util.AttributeSet;
import android.util.Log;
-import android.util.TypedValue;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
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.util.TvSettings;
public class PinDialogFragment extends SafeDismissDialogFragment {
@@ -77,17 +66,12 @@
private static final int MAX_WRONG_PIN_COUNT = 5;
private static final int DISABLE_PIN_DURATION_MILLIS = 60 * 1000; // 1 minute
- private static final String INITIAL_TEXT = "—";
private static final String TRACKER_LABEL = "Pin dialog";
private static final String ARGS_TYPE = "args_type";
private static final String ARGS_RATING = "args_rating";
public static final String DIALOG_TAG = PinDialogFragment.class.getName();
- private static final int NUMBER_PICKERS_RES_ID[] = {
- R.id.first, R.id.second, R.id.third, R.id.fourth
- };
-
private int mType;
private int mRequestType;
private boolean mPinChecked;
@@ -96,7 +80,7 @@
private TextView mWrongPinView;
private View mEnterPinView;
private TextView mTitleView;
- private PinNumberPicker[] mPickers;
+ private PinPicker mPicker;
private SharedPreferences mSharedPreferences;
private String mPrevPin;
private String mPin;
@@ -140,7 +124,6 @@
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dlg = super.onCreateDialog(savedInstanceState);
dlg.getWindow().getAttributes().windowAnimations = R.style.pin_dialog_animation;
- PinNumberPicker.loadResources(dlg.getContext());
return dlg;
}
@@ -171,6 +154,14 @@
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(
+ view -> {
+ String pin = getPinInput();
+ if (!TextUtils.isEmpty(pin)) {
+ done(pin);
+ }
+ });
if (TextUtils.isEmpty(getPin())) {
// If PIN isn't set, user should set a PIN.
// Successfully setting a new set is considered as entering correct PIN.
@@ -210,31 +201,13 @@
}
}
- mPickers = new PinNumberPicker[NUMBER_PICKERS_RES_ID.length];
- for (int i = 0; i < NUMBER_PICKERS_RES_ID.length; i++) {
- mPickers[i] = (PinNumberPicker) v.findViewById(NUMBER_PICKERS_RES_ID[i]);
- mPickers[i].setValueRangeAndResetText(0, 9);
- mPickers[i].setPinDialogFragment(this);
- mPickers[i].updateFocus(false);
- }
- for (int i = 0; i < NUMBER_PICKERS_RES_ID.length - 1; i++) {
- mPickers[i].setNextNumberPicker(mPickers[i + 1]);
- }
-
if (mType != PIN_DIALOG_TYPE_NEW_PIN) {
updateWrongPin();
}
+ mPicker.requestFocus();
return v;
}
- private final Runnable mUpdateEnterPinRunnable =
- new Runnable() {
- @Override
- public void run() {
- updateWrongPin();
- }
- };
-
private void updateWrongPin() {
if (getActivity() == null) {
// The activity is already detached. No need to update.
@@ -257,7 +230,8 @@
R.plurals.pin_enter_countdown,
remainingSeconds,
remainingSeconds));
- mHandler.postDelayed(mUpdateEnterPinRunnable, 1000);
+
+ mHandler.postDelayed(this::updateWrongPin, 1000);
}
}
@@ -364,383 +338,11 @@
}
private String getPinInput() {
- String result = "";
- try {
- for (PinNumberPicker pnp : mPickers) {
- pnp.updateText();
- result += pnp.getValue();
- }
- } catch (IllegalStateException e) {
- result = "";
- }
- return result;
+ return mPicker.getPinInput();
}
private void resetPinInput() {
- for (PinNumberPicker pnp : mPickers) {
- pnp.setValueRangeAndResetText(0, 9);
- }
- mPickers[0].requestFocus();
- }
-
- public static class PinNumberPicker extends FrameLayout {
- private static final int NUMBER_VIEWS_RES_ID[] = {
- R.id.previous2_number,
- R.id.previous_number,
- R.id.current_number,
- R.id.next_number,
- R.id.next2_number
- };
- private static final int CURRENT_NUMBER_VIEW_INDEX = 2;
- private static final int NOT_INITIALIZED = Integer.MIN_VALUE;
-
- private static Animator sFocusedNumberEnterAnimator;
- private static Animator sFocusedNumberExitAnimator;
- private static Animator sAdjacentNumberEnterAnimator;
- private static Animator sAdjacentNumberExitAnimator;
-
- private static float sAlphaForFocusedNumber;
- private static float sAlphaForAdjacentNumber;
-
- private int mMinValue;
- private int mMaxValue;
- private int mCurrentValue;
- // a value for setting mCurrentValue at the end of scroll animation.
- private int mNextValue;
- private final int mNumberViewHeight;
- private PinDialogFragment mDialog;
- private PinNumberPicker mNextNumberPicker;
- private boolean mCancelAnimation;
-
- private final View mNumberViewHolder;
- // When the PinNumberPicker has focus, mBackgroundView will show the focused background.
- // Also, this view is used for handling the text change animation of the current number
- // view which is required when the current number view text is changing from INITIAL_TEXT
- // to "0".
- private final TextView mBackgroundView;
- private final TextView[] mNumberViews;
- private final AnimatorSet mFocusGainAnimator;
- private final AnimatorSet mFocusLossAnimator;
- private final AnimatorSet mScrollAnimatorSet;
-
- public PinNumberPicker(Context context) {
- this(context, null);
- }
-
- public PinNumberPicker(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PinNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public PinNumberPicker(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- View view = inflate(context, R.layout.pin_number_picker, this);
- mNumberViewHolder = view.findViewById(R.id.number_view_holder);
- mBackgroundView = (TextView) view.findViewById(R.id.focused_background);
- mNumberViews = new TextView[NUMBER_VIEWS_RES_ID.length];
- for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
- mNumberViews[i] = (TextView) view.findViewById(NUMBER_VIEWS_RES_ID[i]);
- }
- Resources resources = context.getResources();
- mNumberViewHeight =
- resources.getDimensionPixelSize(R.dimen.pin_number_picker_text_view_height);
-
- mNumberViewHolder.setOnFocusChangeListener(
- new OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- updateFocus(true);
- }
- });
-
- mNumberViewHolder.setOnKeyListener(
- new OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- {
- if (mCancelAnimation) {
- mScrollAnimatorSet.end();
- }
- if (!mScrollAnimatorSet.isRunning()) {
- mCancelAnimation = false;
- if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
- mNextValue =
- adjustValueInValidRange(
- mCurrentValue + 1);
- startScrollAnimation(true);
- } else {
- mNextValue =
- adjustValueInValidRange(
- mCurrentValue - 1);
- startScrollAnimation(false);
- }
- }
- return true;
- }
- }
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- {
- mCancelAnimation = true;
- return true;
- }
- }
- }
- return false;
- }
- });
- mNumberViewHolder.setScrollY(mNumberViewHeight);
-
- mFocusGainAnimator = new AnimatorSet();
- mFocusGainAnimator.playTogether(
- ObjectAnimator.ofFloat(
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1],
- "alpha",
- 0f,
- sAlphaForAdjacentNumber),
- ObjectAnimator.ofFloat(
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX],
- "alpha",
- sAlphaForFocusedNumber,
- 0f),
- ObjectAnimator.ofFloat(
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1],
- "alpha",
- 0f,
- sAlphaForAdjacentNumber),
- ObjectAnimator.ofFloat(mBackgroundView, "alpha", 0f, 1f));
- mFocusGainAnimator.setDuration(
- context.getResources().getInteger(android.R.integer.config_shortAnimTime));
- mFocusGainAnimator.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setText(
- mBackgroundView.getText());
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha(
- sAlphaForFocusedNumber);
- mBackgroundView.setText("");
- }
- });
-
- mFocusLossAnimator = new AnimatorSet();
- mFocusLossAnimator.playTogether(
- ObjectAnimator.ofFloat(
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1],
- "alpha",
- sAlphaForAdjacentNumber,
- 0f),
- ObjectAnimator.ofFloat(
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1],
- "alpha",
- sAlphaForAdjacentNumber,
- 0f),
- ObjectAnimator.ofFloat(mBackgroundView, "alpha", 1f, 0f));
- mFocusLossAnimator.setDuration(
- context.getResources().getInteger(android.R.integer.config_shortAnimTime));
-
- mScrollAnimatorSet = new AnimatorSet();
- mScrollAnimatorSet.setDuration(
- context.getResources().getInteger(R.integer.pin_number_scroll_duration));
- mScrollAnimatorSet.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Set mCurrent value when scroll animation is finished.
- mCurrentValue = mNextValue;
- updateText();
- mNumberViewHolder.setScrollY(mNumberViewHeight);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha(
- sAlphaForAdjacentNumber);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha(
- sAlphaForFocusedNumber);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha(
- sAlphaForAdjacentNumber);
- }
- });
- }
-
- static void loadResources(Context context) {
- if (sFocusedNumberEnterAnimator == null) {
- TypedValue outValue = new TypedValue();
- context.getResources()
- .getValue(R.dimen.pin_alpha_for_focused_number, outValue, true);
- sAlphaForFocusedNumber = outValue.getFloat();
- context.getResources()
- .getValue(R.dimen.pin_alpha_for_adjacent_number, outValue, true);
- sAlphaForAdjacentNumber = outValue.getFloat();
-
- sFocusedNumberEnterAnimator =
- AnimatorInflater.loadAnimator(context, R.animator.pin_focused_number_enter);
- sFocusedNumberExitAnimator =
- AnimatorInflater.loadAnimator(context, R.animator.pin_focused_number_exit);
- sAdjacentNumberEnterAnimator =
- AnimatorInflater.loadAnimator(
- context, R.animator.pin_adjacent_number_enter);
- sAdjacentNumberExitAnimator =
- AnimatorInflater.loadAnimator(context, R.animator.pin_adjacent_number_exit);
- }
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- int keyCode = event.getKeyCode();
- if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
- mNextValue = adjustValueInValidRange(keyCode - KeyEvent.KEYCODE_0);
- updateFocus(false);
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
- || keyCode == KeyEvent.KEYCODE_ENTER) {
- if (mNextNumberPicker == null) {
- String pin = mDialog.getPinInput();
- if (!TextUtils.isEmpty(pin)) {
- mDialog.done(pin);
- }
- } else {
- mNextNumberPicker.requestFocus();
- }
- return true;
- }
- }
- return super.dispatchKeyEvent(event);
- }
-
- void startScrollAnimation(boolean scrollUp) {
- mFocusGainAnimator.end();
- mFocusLossAnimator.end();
- final ValueAnimator scrollAnimator =
- ValueAnimator.ofInt(0, scrollUp ? mNumberViewHeight : -mNumberViewHeight);
- scrollAnimator.addUpdateListener(
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int value = (Integer) animation.getAnimatedValue();
- mNumberViewHolder.setScrollY(value + mNumberViewHeight);
- }
- });
- scrollAnimator.setDuration(
- getResources().getInteger(R.integer.pin_number_scroll_duration));
-
- if (scrollUp) {
- sAdjacentNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1]);
- sFocusedNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX]);
- sFocusedNumberEnterAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1]);
- sAdjacentNumberEnterAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 2]);
- } else {
- sAdjacentNumberEnterAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 2]);
- sFocusedNumberEnterAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1]);
- sFocusedNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX]);
- sAdjacentNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1]);
- }
-
- mScrollAnimatorSet.playTogether(
- scrollAnimator,
- sAdjacentNumberExitAnimator,
- sFocusedNumberExitAnimator,
- sFocusedNumberEnterAnimator,
- sAdjacentNumberEnterAnimator);
- mScrollAnimatorSet.start();
- }
-
- void setValueRangeAndResetText(int min, int max) {
- if (min > max) {
- throw new IllegalArgumentException(
- "The min value should be greater than or equal to the max value");
- } else if (min == NOT_INITIALIZED) {
- throw new IllegalArgumentException(
- "The min value should be greater than Integer.MIN_VALUE.");
- }
- mMinValue = min;
- mMaxValue = max;
- mNextValue = mCurrentValue = NOT_INITIALIZED;
- for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
- mNumberViews[i].setText(i == CURRENT_NUMBER_VIEW_INDEX ? INITIAL_TEXT : "");
- }
- mBackgroundView.setText(INITIAL_TEXT);
- }
-
- void setPinDialogFragment(PinDialogFragment dlg) {
- mDialog = dlg;
- }
-
- void setNextNumberPicker(PinNumberPicker picker) {
- mNextNumberPicker = picker;
- }
-
- int getValue() {
- if (mCurrentValue < mMinValue || mCurrentValue > mMaxValue) {
- throw new IllegalStateException("Value is not set");
- }
- return mCurrentValue;
- }
-
- void updateFocus(boolean withAnimation) {
- mScrollAnimatorSet.end();
- mFocusGainAnimator.end();
- mFocusLossAnimator.end();
- updateText();
- if (mNumberViewHolder.isFocused()) {
- if (withAnimation) {
- mBackgroundView.setText(String.valueOf(mCurrentValue));
- mFocusGainAnimator.start();
- } else {
- mBackgroundView.setAlpha(1f);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha(sAlphaForAdjacentNumber);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha(sAlphaForAdjacentNumber);
- }
- } else {
- if (withAnimation) {
- mFocusLossAnimator.start();
- } else {
- mBackgroundView.setAlpha(0f);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha(0f);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha(0f);
- }
- mNumberViewHolder.setScrollY(mNumberViewHeight);
- }
- }
-
- private void updateText() {
- boolean wasNotInitialized = false;
- if (mNumberViewHolder.isFocused() && mCurrentValue == NOT_INITIALIZED) {
- mNextValue = mCurrentValue = mMinValue;
- wasNotInitialized = true;
- }
- if (mCurrentValue >= mMinValue && mCurrentValue <= mMaxValue) {
- for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
- if (wasNotInitialized && i == CURRENT_NUMBER_VIEW_INDEX) {
- // In order to show the text change animation, keep the text of
- // mNumberViews[CURRENT_NUMBER_VIEW_INDEX].
- } else {
- mNumberViews[i].setText(
- String.valueOf(
- adjustValueInValidRange(
- mCurrentValue - CURRENT_NUMBER_VIEW_INDEX + i)));
- }
- }
- }
- }
-
- private int adjustValueInValidRange(int value) {
- int interval = mMaxValue - mMinValue + 1;
- if (value < mMinValue - interval || value > mMaxValue + interval) {
- throw new IllegalArgumentException(
- "The value( " + value + ") is too small or too big to adjust");
- }
- return (value < mMinValue)
- ? value + interval
- : (value > mMaxValue) ? value - interval : value;
- }
+ mPicker.resetPinInput();
}
/**
diff --git a/src/com/android/tv/dialog/picker/PinPicker.java b/src/com/android/tv/dialog/picker/PinPicker.java
new file mode 100644
index 0000000..f501dfd
--- /dev/null
+++ b/src/com/android/tv/dialog/picker/PinPicker.java
@@ -0,0 +1,131 @@
+/*
+ * 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/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java
index 2b4ecbf..0053650 100644
--- a/src/com/android/tv/dvr/DvrDataManagerImpl.java
+++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java
@@ -16,7 +16,6 @@
package com.android.tv.dvr;
-import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -49,21 +48,23 @@
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.AsyncDvrDbTask.AsyncAddScheduleTask;
-import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncAddSeriesRecordingTask;
-import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDeleteScheduleTask;
-import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDeleteSeriesRecordingTask;
-import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQueryScheduleTask;
-import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQuerySeriesRecordingTask;
-import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncUpdateScheduleTask;
-import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncUpdateSeriesRecordingTask;
+import com.android.tv.dvr.provider.DvrDbFuture.AddScheduleFuture;
+import com.android.tv.dvr.provider.DvrDbFuture.AddSeriesRecordingFuture;
+import com.android.tv.dvr.provider.DvrDbFuture.DeleteScheduleFuture;
+import com.android.tv.dvr.provider.DvrDbFuture.DeleteSeriesRecordingFuture;
+import com.android.tv.dvr.provider.DvrDbFuture.DvrQueryScheduleFuture;
+import com.android.tv.dvr.provider.DvrDbFuture.DvrQuerySeriesRecordingFuture;
+import com.android.tv.dvr.provider.DvrDbFuture.UpdateScheduleFuture;
+import com.android.tv.dvr.provider.DvrDbFuture.UpdateSeriesRecordingFuture;
import com.android.tv.dvr.provider.DvrDbSync;
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.Filter;
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;
@@ -73,6 +74,7 @@
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
/** DVR Data manager to handle recordings and schedules. */
@MainThread
@@ -106,8 +108,7 @@
@Override
public void onChange(boolean selfChange, final @Nullable Uri uri) {
- RecordedProgramsQueryTask task =
- new RecordedProgramsQueryTask(mContext.getContentResolver(), uri);
+ RecordedProgramsQueryTask task = new RecordedProgramsQueryTask(uri);
task.executeOnDbThread();
mPendingTasks.add(task);
}
@@ -116,6 +117,9 @@
private boolean mDvrLoadFinished;
private boolean mRecordedProgramLoadFinished;
private final Set<AsyncTask> mPendingTasks = new ArraySet<>();
+ private final Set<Future> mPendingDvrFuture = new ArraySet<>();
+ // TODO(b/79207567) make sure Future is not stopped at writing.
+ private final Set<Future> mNoStopFuture = new ArraySet<>();
private DvrDbSync mDbSync;
private RecordingStorageStatusManager mStorageStatusManager;
@@ -154,13 +158,27 @@
}
};
+ private final FutureCallback<Void> removeFromSetOnCompletion =
+ new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ mNoStopFuture.remove(this);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.w(TAG, "Failed to execute.", t);
+ mNoStopFuture.remove(this);
+ }
+ };
+
private static <T> List<T> moveElements(
- HashMap<Long, T> from, HashMap<Long, T> to, Filter<T> filter) {
+ HashMap<Long, T> from, HashMap<Long, T> to, Predicate<T> filter) {
List<T> moved = new ArrayList<>();
Iterator<Entry<Long, T>> iter = from.entrySet().iterator();
while (iter.hasNext()) {
Entry<Long, T> entry = iter.next();
- if (filter.filter(entry.getValue())) {
+ if (filter.apply(entry.getValue())) {
to.put(entry.getKey(), entry.getValue());
iter.remove();
moved.add(entry.getValue());
@@ -181,134 +199,143 @@
public void start() {
mInputManager.addCallback(mInputCallback);
mStorageStatusManager.addListener(mStorageMountChangedListener);
- AsyncDvrQuerySeriesRecordingTask dvrQuerySeriesRecordingTask =
- new AsyncDvrQuerySeriesRecordingTask(mContext) {
- @Override
- protected void onCancelled(List<SeriesRecording> seriesRecordings) {
- mPendingTasks.remove(this);
- }
-
- @Override
- protected void onPostExecute(List<SeriesRecording> seriesRecordings) {
- mPendingTasks.remove(this);
- long maxId = 0;
- HashSet<String> seriesIds = new HashSet<>();
- for (SeriesRecording r : seriesRecordings) {
- if (SoftPreconditions.checkState(
- !seriesIds.contains(r.getSeriesId()),
- TAG,
- "Skip loading series recording with duplicate series ID: "
- + r)) {
- seriesIds.add(r.getSeriesId());
- if (isInputAvailable(r.getInputId())) {
- mSeriesRecordings.put(r.getId(), r);
- mSeriesId2SeriesRecordings.put(r.getSeriesId(), r);
- } else {
- mSeriesRecordingsForRemovedInput.put(r.getId(), r);
- }
- }
- if (maxId < r.getId()) {
- maxId = r.getId();
- }
- }
- IdGenerator.SERIES_RECORDING.setMaxId(maxId);
- }
- };
- dvrQuerySeriesRecordingTask.executeOnDbThread();
- mPendingTasks.add(dvrQuerySeriesRecordingTask);
- AsyncDvrQueryScheduleTask dvrQueryScheduleTask =
- new AsyncDvrQueryScheduleTask(mContext) {
- @Override
- protected void onCancelled(List<ScheduledRecording> scheduledRecordings) {
- mPendingTasks.remove(this);
- }
-
- @SuppressLint("SwitchIntDef")
- @Override
- protected void onPostExecute(List<ScheduledRecording> result) {
- mPendingTasks.remove(this);
- long maxId = 0;
- int reasonNotStarted =
- ScheduledRecording
- .FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED;
- List<ScheduledRecording> toUpdate = new ArrayList<>();
- List<ScheduledRecording> toDelete = new ArrayList<>();
- for (ScheduledRecording r : result) {
- if (!isInputAvailable(r.getInputId())) {
- mScheduledRecordingsForRemovedInput.put(r.getId(), r);
- } else if (r.getState() == ScheduledRecording.STATE_RECORDING_DELETED) {
- getDeletedScheduleMap().put(r.getProgramId(), r);
- } else {
- mScheduledRecordings.put(r.getId(), r);
- if (r.getProgramId() != ScheduledRecording.ID_NOT_SET) {
- mProgramId2ScheduledRecordings.put(r.getProgramId(), r);
- }
- // Adjust the state of the schedules before DB loading is finished.
- switch (r.getState()) {
- case ScheduledRecording.STATE_RECORDING_IN_PROGRESS:
- if (r.getEndTimeMs() <= mClock.currentTimeMillis()) {
- int reason =
- ScheduledRecording.FAILED_REASON_NOT_FINISHED;
-