Skip to content

Commit ffc7e94

Browse files
authored
Merge pull request #2697 from DataDog/xgouchet/tv_sample_app
Add Android TV sample app
2 parents 4912bc6 + 08e7f0d commit ffc7e94

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1721
-6
lines changed

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ tasks.register("assembleSampleRelease") {
9494
dependsOn(
9595
":sample:kotlin:assembleUs1Release",
9696
":sample:wear:assembleUs1Release",
97-
":sample:vendor-lib:assembleRelease"
97+
":sample:vendor-lib:assembleRelease",
98+
":sample:tv:assembleRelease"
9899
)
99100
}
100101

buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ object AndroidConfig {
1717

1818
const val TARGET_SDK = 36
1919
const val MIN_SDK = 21
20-
const val MIN_SDK_FOR_WEAR = 23
20+
const val MIN_SDK_FOR_WEAR_AND_TV = 23
2121
const val BUILD_TOOLS_VERSION = "36.0.0"
2222

2323
val VERSION = Version(2, 23, 0, Version.Type.Snapshot)

buildSrc/src/main/kotlin/com/datadog/gradle/config/FlavorBuildConfig.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,20 @@ fun configureFlavorForSampleApp(
105105
"\"${flavor.name.uppercase(Locale.US)}\""
106106
)
107107
}
108+
109+
@Suppress("UnstableApiUsage")
110+
fun ApplicationDefaultConfig.configureFlavorForTvApp(
111+
rootDir: File
112+
) {
113+
val config = sampleAppConfig("${rootDir.absolutePath}/config/tv.json")
114+
buildConfigField(
115+
"String",
116+
"DD_RUM_APPLICATION_ID",
117+
"\"${config.rumApplicationId}\""
118+
)
119+
buildConfigField(
120+
"String",
121+
"DD_CLIENT_TOKEN",
122+
"\"${config.token}\""
123+
)
124+
}

gradle/libs.versions.toml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ androidXConstraintLayout = "2.0.4"
2222
androidXCore = "1.3.1"
2323
androidXFragment = "1.2.4"
2424
androidXLeanback = "1.0.0"
25-
androidXLifecycleRuntimeCompose = "2.8.7"
25+
androidXLifecycle = "2.8.7"
2626
androidXMetrics = "1.0.0-beta02"
2727
androidXMultidex = "2.0.1"
2828
androidXNavigation = "2.7.7"
@@ -91,6 +91,8 @@ room = "2.5.1"
9191
rxJava3 = "3.0.0"
9292
timber = "5.0.1"
9393
coroutines = "1.4.2"
94+
exoplayer = "2.19.1"
95+
newPipeExtractor = "v0.24.6"
9496

9597
# Local Server
9698
ktor = "2.3.13"
@@ -118,7 +120,7 @@ sqlDelightGradlePlugin = { module = "com.squareup.sqldelight:gradle-plugin", ver
118120
binaryCompatibilityGradlePlugin = { module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "binaryCompatibility" }
119121
dependencyLicenseGradlePlugin = { module = "com.datadoghq:dependency-license", version.ref = "dependencyLicense" }
120122
versionsGradlePlugin = { module = "com.github.ben-manes:gradle-versions-plugin", version.ref = "versionsGradlePlugin" }
121-
kotlinxSerializationPlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin"}
123+
kotlinxSerializationPlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
122124

123125
# Annotation processors
124126
glideCompiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
@@ -142,15 +144,19 @@ androidXAppCompat = { module = "androidx.appcompat:appcompat", version.ref = "an
142144
androidXCollection = { module = "androidx.collection:collection", version.ref = "androidXCollection" }
143145
androidXConstraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidXConstraintLayout" }
144146
androidXCore = { module = "androidx.core:core", version.ref = "androidXCore" }
147+
androidXCoreKtx = { module = "androidx.core:core-ktx", version.ref = "androidXCore" }
145148
androidXFragment = { module = "androidx.fragment:fragment", version.ref = "androidXFragment" }
146149
androidXLeanback = { module = "androidx.leanback:leanback", version.ref = "androidXLeanback" }
147150
androidXMetrics = { module = "androidx.metrics:metrics-performance", version.ref = "androidXMetrics" }
148151
androidXMultidex = { module = "androidx.multidex:multidex", version.ref = "androidXMultidex" }
149152
androidXNavigationFragment = { module = "androidx.navigation:navigation-fragment", version.ref = "androidXNavigation" }
150153
androidXNavigationRuntime = { module = "androidx.navigation:navigation-runtime", version.ref = "androidXNavigation" }
154+
androidXNavigationUIKtx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidXNavigation" }
151155
androidXRecyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "androidXRecyclerView" }
152156
androidXWorkManager = { module = "androidx.work:work-runtime", version.ref = "androidXWorkManager" }
153-
androidXLifecycleCompose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidXLifecycleRuntimeCompose" }
157+
androidXLifecycleCompose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidXLifecycle" }
158+
androidXLifecycleLiveDataKtx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidXLifecycle" }
159+
androidXLifecycleViewModelKtx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidXLifecycle" }
154160
daggerLib = { module = "com.google.dagger:dagger", version.ref = "dagger" }
155161
daggerCompiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
156162
googleAccompanistAppCompatTheme = { module = "com.google.accompanist:accompanist-appcompat-theme", version.ref = "googleAccompanist" }
@@ -244,6 +250,11 @@ timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
244250

245251
coroutinesCore = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
246252

253+
exoplayer = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" }
254+
exoplayerDataSource = { module = "com.google.android.exoplayer:exoplayer-datasource", version.ref = "exoplayer" }
255+
exoplayerOkHttp = { module = "com.google.android.exoplayer:extension-okhttp", version.ref = "exoplayer" }
256+
newPipeExtractor = { module = "com.github.TeamNewPipe.NewPipeExtractor:extractor", version.ref = "newPipeExtractor" }
257+
247258
# Local Server
248259
ktorCore = { module = "io.ktor:ktor", version.ref = "ktor" }
249260
ktorGson = { module = "io.ktor:ktor-serialization-gson", version.ref = "ktor" }
@@ -334,6 +345,12 @@ glide = [
334345
"glideOkHttp3",
335346
]
336347

348+
exoplayer = [
349+
"exoplayer",
350+
"exoplayerDataSource",
351+
"exoplayerOkHttp"
352+
]
353+
337354
ktor = [
338355
"ktorCore",
339356
"ktorGson",

sample/tv/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

sample/tv/build.gradle.kts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
import com.datadog.gradle.config.AndroidConfig
8+
import com.datadog.gradle.config.configureFlavorForTvApp
9+
import com.datadog.gradle.config.dependencyUpdateConfig
10+
import com.datadog.gradle.config.java17
11+
import com.datadog.gradle.config.junitConfig
12+
import com.datadog.gradle.config.kotlinConfig
13+
import com.datadog.gradle.config.taskConfig
14+
15+
plugins {
16+
id("com.android.application")
17+
kotlin("android")
18+
id("com.github.ben-manes.versions")
19+
alias(libs.plugins.datadogGradlePlugin)
20+
}
21+
22+
android {
23+
namespace = "com.datadog.android.tv.sample"
24+
compileSdk = AndroidConfig.TARGET_SDK
25+
buildToolsVersion = AndroidConfig.BUILD_TOOLS_VERSION
26+
27+
defaultConfig {
28+
minSdk = AndroidConfig.MIN_SDK_FOR_WEAR_AND_TV
29+
targetSdk = AndroidConfig.TARGET_SDK
30+
versionCode = AndroidConfig.VERSION.code
31+
versionName = AndroidConfig.VERSION.name
32+
multiDexEnabled = true
33+
34+
vectorDrawables.useSupportLibrary = true
35+
36+
configureFlavorForTvApp(project.rootDir)
37+
}
38+
39+
compileOptions {
40+
java17()
41+
}
42+
43+
buildTypes {
44+
release {
45+
isMinifyEnabled = false
46+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
47+
}
48+
}
49+
50+
kotlinOptions {
51+
jvmTarget = "17"
52+
}
53+
54+
buildFeatures {
55+
buildConfig = true
56+
}
57+
58+
sourceSets.named("main") {
59+
java.srcDir("src/main/kotlin")
60+
}
61+
sourceSets.named("test") {
62+
java.srcDir("src/test/kotlin")
63+
}
64+
sourceSets.named("androidTest") {
65+
java.srcDir("src/androidTest/kotlin")
66+
}
67+
}
68+
69+
repositories {
70+
maven { setUrl("https://jitpack.io") }
71+
}
72+
73+
dependencies {
74+
75+
implementation(project(":dd-sdk-android-core"))
76+
implementation(project(":features:dd-sdk-android-rum"))
77+
implementation(project(":features:dd-sdk-android-logs"))
78+
implementation(project(":features:dd-sdk-android-session-replay"))
79+
implementation(project(":features:dd-sdk-android-session-replay-material"))
80+
implementation(project(":integrations:dd-sdk-android-okhttp"))
81+
implementation(project(":integrations:dd-sdk-android-timber"))
82+
implementation(project(":integrations:dd-sdk-android-tv"))
83+
84+
implementation(libs.kotlin)
85+
86+
// Android dependencies
87+
implementation(libs.androidXCore)
88+
implementation(libs.androidXCoreKtx)
89+
implementation(libs.androidXAppCompat)
90+
implementation(libs.googleMaterial)
91+
implementation(libs.androidXRecyclerView)
92+
implementation(libs.androidXConstraintLayout)
93+
implementation(libs.androidXLifecycleLiveDataKtx)
94+
implementation(libs.androidXLifecycleViewModelKtx)
95+
implementation(libs.bundles.androidXNavigation)
96+
implementation(libs.androidXNavigationUIKtx)
97+
98+
// Network
99+
implementation(libs.okHttp)
100+
implementation(libs.gson)
101+
102+
// Misc
103+
implementation(libs.timber)
104+
105+
// Video
106+
implementation(libs.bundles.exoplayer)
107+
implementation(libs.newPipeExtractor)
108+
}
109+
110+
kotlinConfig(evaluateWarningsAsErrors = false)
111+
taskConfig<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
112+
compilerOptions {
113+
freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn")
114+
}
115+
}
116+
junitConfig()
117+
dependencyUpdateConfig()

sample/tv/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
4+
~ This product includes software developed at Datadog (https://www.datadoghq.com/).
5+
~ Copyright 2016-Present Datadog, Inc.
6+
-->
7+
8+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
9+
xmlns:tools="http://schemas.android.com/tools">
10+
11+
<uses-feature android:name="android.software.leanback" android:required="true" />
12+
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
13+
14+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
15+
16+
<application
17+
android:allowBackup="true"
18+
android:name=".TvSampleApplication"
19+
android:icon="@mipmap/ic_launcher"
20+
android:label="@string/app_name"
21+
android:banner="@drawable/banner"
22+
android:roundIcon="@mipmap/ic_launcher_round"
23+
android:supportsRtl="true"
24+
android:theme="@style/SampleTv.Theme">
25+
26+
<activity
27+
android:name=".HomeActivity"
28+
android:exported="true"
29+
android:label="@string/app_name"
30+
android:resizeableActivity="true"
31+
tools:targetApi="33">
32+
33+
<intent-filter>
34+
<action android:name="android.intent.action.MAIN" />
35+
36+
<category android:name="android.intent.category.LAUNCHER" />
37+
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
38+
</intent-filter>
39+
</activity>
40+
41+
<activity android:name=".PlayerActivity" />
42+
</application>
43+
44+
</manifest>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.tv.sample
8+
9+
import android.annotation.SuppressLint
10+
import android.os.Build
11+
import android.view.LayoutInflater
12+
import android.view.View
13+
import android.view.ViewGroup
14+
import android.widget.TextView
15+
import androidx.recyclerview.widget.RecyclerView
16+
import com.datadog.android.tv.sample.model.Episode
17+
18+
internal class EpisodeRecyclerView private constructor() {
19+
20+
class Adapter(
21+
val onEpisodeSelected: (Episode?, Int) -> Unit
22+
) : RecyclerView.Adapter<ViewHolder>() {
23+
24+
private val data: MutableList<Episode> = mutableListOf()
25+
26+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
27+
val itemView = LayoutInflater.from(parent.context).inflate(
28+
R.layout.episode_item,
29+
parent,
30+
false
31+
)
32+
return ViewHolder(itemView, onEpisodeSelected)
33+
}
34+
35+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
36+
holder.render(data[position])
37+
}
38+
39+
override fun getItemCount(): Int {
40+
return data.size
41+
}
42+
43+
@SuppressLint("NotifyDataSetChanged")
44+
internal fun updateData(newData: List<Episode>) {
45+
data.clear()
46+
data.addAll(newData)
47+
notifyDataSetChanged()
48+
onEpisodeSelected(null, -1)
49+
}
50+
}
51+
52+
class ViewHolder(
53+
itemView: View,
54+
onEpisodeSelected: (Episode, Int) -> Unit
55+
) : RecyclerView.ViewHolder(itemView) {
56+
57+
lateinit var episode: Episode
58+
59+
val textView = itemView.findViewById<TextView>(R.id.title)
60+
61+
init {
62+
itemView.setOnClickListener {
63+
updateEpisodeHighlight(true)
64+
onEpisodeSelected(episode, 0)
65+
}
66+
itemView.setOnFocusChangeListener { view, b ->
67+
updateEpisodeHighlight(b)
68+
onEpisodeSelected(episode, 0)
69+
}
70+
}
71+
72+
private fun updateEpisodeHighlight(selected: Boolean) {
73+
val colorRes = if (selected) R.color.dd_purple_200 else R.color.text_default
74+
val textColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
75+
textView.resources.getColor(colorRes, null)
76+
} else {
77+
@Suppress("DEPRECATION")
78+
textView.resources.getColor(colorRes)
79+
}
80+
textView.setTextColor(textColor)
81+
}
82+
83+
fun render(episode: Episode) {
84+
this.episode = episode
85+
textView.text = episode.title
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)