Add preference to disable tvprovider integration

This commit is contained in:
Niels van Velzen authored and Niels van Velzen committed 2026-09-24 18:02:35 +02:00
1 parent 11ca1ce20d
commit 51aadf939e
4 files changed
+62 -12

No files matched your search

@@ -5,6 +5,7 @@ import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.core.content.edit
import androidx.core.content.res.ResourcesCompat
@@ -72,6 +73,14 @@ class LeanbackChannelWorker(
) : CoroutineWorker(context, workerParams), KoinComponent {
companion object {
private const val PERIODIC_UPDATE_REQUEST_NAME = "LeanbackChannelPeriodicUpdateRequest"
private const val CHANNEL_STORE_NAME = "leanback_channels"
/**
* Check if the app can use Android TV home screen channels on this device.
*/
fun isAvailable(context: Context) = AndroidVersion.isAtLeastO &&
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) &&
context.packageManager.resolveContentProvider(TvContractCompat.AUTHORITY, 0) != null
suspend fun enqueue(workManager: WorkManager) {
workManager.enqueueUniquePeriodicWork(
@@ -89,21 +98,17 @@ class LeanbackChannelWorker(
private val userViewsRepository by inject<UserViewsRepository>()
private val imageHelper by inject<ImageHelper>()
/**
* Check if the app can use Leanback features and is API level 26 or higher.
*/
private val isSupported = AndroidVersion.isAtLeastO &&
// Check for leanback support
context.packageManager.hasSystemFeature("android.software.leanback")
// Check for "android.media.tv" provider to workaround a false-positive in the previous check
&& context.packageManager.resolveContentProvider(TvContractCompat.AUTHORITY, 0) != null
/**
* Update all channels for the currently authenticated user.
*/
override suspend fun doWork(): Result = when {
// Fail when not supported
!isSupported -> Result.failure()
// Succeed immediatly when not available
!isAvailable(context) -> Result.success()
// Clear all integration data when disabled
!userPreferences[UserPreferences.tvProviderEnabled] -> {
clearChannels()
Result.success()
}
// Retry later if no authenticated user is found
!api.isUsable -> Result.retry()
else -> try {
@@ -194,13 +199,28 @@ class LeanbackChannelWorker(
}
}
/**
* Remove all channels and programs managed by the Android TV home screen integration.
*/
private fun clearChannels() {
context.contentResolver.delete(TvContractCompat.PreviewPrograms.CONTENT_URI, null, null)
context.contentResolver.delete(WatchNextPrograms.CONTENT_URI, null, null)
val store = context.getSharedPreferences(CHANNEL_STORE_NAME, Context.MODE_PRIVATE)
store.all.values
.filterIsInstance<String>()
.map(Uri::parse)
.forEach { uri -> context.contentResolver.delete(uri, null, null) }
store.edit { clear() }
}
/**
* Get the uri for a channel or create it if it doesn't exist. Uses the [settings] parameter to
* update or create the channel. The [name] parameter is used to store the id and should be
* unique.
*/
private fun getChannelUri(name: String, settings: Channel, default: Boolean = false): Uri? {
val store = context.getSharedPreferences("leanback_channels", Context.MODE_PRIVATE)
val store = context.getSharedPreferences(CHANNEL_STORE_NAME, Context.MODE_PRIVATE)
var uri: Uri? = null
// Try and re-use our existing channel definition
@@ -312,6 +312,11 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
* Stores the interval for the photo player.
*/
var photoPlayerPresentationDelay = longPreference("photo_player_presentation_delay", 8000)
/**
* Integrate with androidx.tvprovider for the Android TV home screen.
*/
var tvProviderEnabled = booleanPreference("tv_provider_enabled", true)
}
init {
@@ -2,11 +2,16 @@ package org.jellyfin.androidtv.ui.settings.screen.customization
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.integration.LeanbackChannelWorker
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.ui.base.Icon
import org.jellyfin.androidtv.ui.base.Text
@@ -22,8 +27,11 @@ import org.koin.compose.koinInject
@Composable
fun SettingsCustomizationScreen() {
val context = LocalContext.current
val router = LocalRouter.current
val userPreferences = koinInject<UserPreferences>()
val workManager = koinInject<WorkManager>()
val leanbackChannelWorkerAvailable = remember(context) { LeanbackChannelWorker.isAvailable(context) }
SettingsColumn {
item {
@@ -89,6 +97,21 @@ fun SettingsCustomizationScreen() {
)
}
if (leanbackChannelWorkerAvailable) item {
var tvProviderEnabled by rememberPreference(userPreferences, UserPreferences.tvProviderEnabled)
ListButton(
headingContent = { Text(stringResource(R.string.tv_provider_enabled)) },
captionContent = { Text(stringResource(R.string.tv_provider_enabled_description)) },
trailingContent = { Checkbox(checked = tvProviderEnabled) },
onClick = {
tvProviderEnabled = !tvProviderEnabled
workManager.enqueue(OneTimeWorkRequestBuilder<LeanbackChannelWorker>().build())
},
modifier = Modifier.focusKey("leanback_channels_enabled")
)
}
item { ListSection(headingContent = { Text(stringResource(R.string.pref_browsing)) }) }
item {
+2
View File
@@ -433,6 +433,8 @@
<string name="home_section_i">Home section %1$d</string>
<string name="home_next_up_max_days">Next up date cutoff</string>
<string name="home_next_up_max_days_disabled">Disabled</string>
<string name="tv_provider_enabled">Integrate with Android TV home</string>
<string name="tv_provider_enabled_description">Show Jellyfin channels and recommendations on the Android TV home screen</string>
<string name="searchable_hint">Search Jellyfin</string>
<string name="searchable_settings_description">Media</string>
<string name="pref_subtitles_background_title">Enable background</string>