diff --git a/app/src/main/java/org/jellyfin/androidtv/auth/ui/ActivityAuthenticationExtensions.kt b/app/src/main/java/org/jellyfin/androidtv/auth/ui/ActivityAuthenticationExtensions.kt new file mode 100644 index 0000000000..15890e8322 --- /dev/null +++ b/app/src/main/java/org/jellyfin/androidtv/auth/ui/ActivityAuthenticationExtensions.kt @@ -0,0 +1,29 @@ +package org.jellyfin.androidtv.auth.ui + +import android.content.Intent +import androidx.fragment.app.FragmentActivity +import org.jellyfin.androidtv.auth.repository.SessionRepository +import org.jellyfin.androidtv.ui.startup.StartupActivity +import org.koin.android.ext.android.inject +import timber.log.Timber + +/** + * Extension function to check authentication. Should be called in [FragmentActivity.onCreate] and + * [FragmentActivity.onResume]. It validates the current session and opens the authentication screen + * when no session is found. + * + * @return whether to proceed creating the activity or not. When `false` is returned the + * [FragmentActivity.finish] function is automatically called. + */ +fun FragmentActivity.validateAuthentication(): Boolean { + val sessionRepository by inject() + + if (sessionRepository.currentSession.value == null) { + Timber.w("Activity ${this::class.qualifiedName} started without a session, bouncing to StartupActivity") + startActivity(Intent(this, StartupActivity::class.java)) + finish() + return false + } + + return true +} diff --git a/app/src/main/java/org/jellyfin/androidtv/di/ActivityLifecycleCallbacksModule.kt b/app/src/main/java/org/jellyfin/androidtv/di/ActivityLifecycleCallbacksModule.kt deleted file mode 100644 index 6e8c3c97ad..0000000000 --- a/app/src/main/java/org/jellyfin/androidtv/di/ActivityLifecycleCallbacksModule.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.jellyfin.androidtv.di - -import android.app.Application -import org.jellyfin.androidtv.ui.shared.AuthenticatedUserCallbacks -import org.koin.dsl.bind -import org.koin.dsl.module - -val activityLifecycleCallbacksModule = module { - single { AuthenticatedUserCallbacks(get()) } bind Application.ActivityLifecycleCallbacks::class -} diff --git a/app/src/main/java/org/jellyfin/androidtv/di/KoinInitializer.kt b/app/src/main/java/org/jellyfin/androidtv/di/KoinInitializer.kt index 4f1a372819..4658c2305f 100644 --- a/app/src/main/java/org/jellyfin/androidtv/di/KoinInitializer.kt +++ b/app/src/main/java/org/jellyfin/androidtv/di/KoinInitializer.kt @@ -12,7 +12,6 @@ class KoinInitializer : Initializer { androidContext(context) modules( - activityLifecycleCallbacksModule, androidModule, appModule, authModule, diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt index 1a806e046a..114823bf6d 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt @@ -13,6 +13,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.launch import org.jellyfin.androidtv.R +import org.jellyfin.androidtv.auth.ui.validateAuthentication import org.jellyfin.androidtv.data.service.BackgroundService import org.jellyfin.androidtv.ui.navigation.NavigationAction import org.jellyfin.androidtv.ui.navigation.NavigationRepository @@ -36,8 +37,11 @@ class MainActivity : FragmentActivity(R.layout.fragment_content_view) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (!validateAuthentication()) return + applyTheme() + backgroundService.attach(this) onBackPressedDispatcher.addCallback(this, backPressedCallback) @@ -61,6 +65,8 @@ class MainActivity : FragmentActivity(R.layout.fragment_content_view) { override fun onResume() { super.onResume() + if (!validateAuthentication()) return + applyTheme() } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/ExternalPlayerActivity.java b/app/src/main/java/org/jellyfin/androidtv/ui/playback/ExternalPlayerActivity.java index f64af84be9..e954871330 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/ExternalPlayerActivity.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/ExternalPlayerActivity.java @@ -17,6 +17,7 @@ import org.jellyfin.androidtv.R; import org.jellyfin.androidtv.auth.repository.UserRepository; +import org.jellyfin.androidtv.auth.ui.ActivityAuthenticationExtensionsKt; import org.jellyfin.androidtv.data.compat.PlaybackException; import org.jellyfin.androidtv.data.compat.StreamInfo; import org.jellyfin.androidtv.data.compat.SubtitleStreamInfo; @@ -106,6 +107,8 @@ public class ExternalPlayerActivity extends FragmentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (!ActivityAuthenticationExtensionsKt.validateAuthentication(this)) return; + backgroundService.getValue().attach(this); mItemsToPlay = mediaManager.getValue().getCurrentVideoQueue(); @@ -126,6 +129,8 @@ protected void onCreate(Bundle savedInstanceState) { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); + if (!ActivityAuthenticationExtensionsKt.validateAuthentication(this)) return; + long playerFinishedTime = System.currentTimeMillis(); Timber.d("Returned from player, result <%d>, extra data <%s>", resultCode, data); org.jellyfin.sdk.model.api.BaseItemDto item = mItemsToPlay.get(mCurrentNdx); diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackOverlayActivity.kt b/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackOverlayActivity.kt index 8beabc0b37..7f080f08af 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackOverlayActivity.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackOverlayActivity.kt @@ -12,6 +12,7 @@ import androidx.fragment.app.FragmentActivity import androidx.fragment.app.add import androidx.fragment.app.commit import org.jellyfin.androidtv.R +import org.jellyfin.androidtv.auth.ui.validateAuthentication import org.jellyfin.androidtv.util.applyTheme import org.koin.android.ext.android.inject @@ -25,6 +26,8 @@ class PlaybackOverlayActivity : FragmentActivity(R.layout.fragment_content_view) public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (!validateAuthentication()) return + applyTheme() // Workaround for Sony Bravia devices that show a "grey" background on HDR videos @@ -43,6 +46,12 @@ class PlaybackOverlayActivity : FragmentActivity(R.layout.fragment_content_view) } } + override fun onResume() { + super.onResume() + + if (!validateAuthentication()) return + } + override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { // Try listener first if (keyListener?.onKey(currentFocus, keyCode, event) == true) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/nextup/NextUpActivity.kt b/app/src/main/java/org/jellyfin/androidtv/ui/playback/nextup/NextUpActivity.kt index 119a294472..b198f7db18 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/nextup/NextUpActivity.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/nextup/NextUpActivity.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.launch import org.jellyfin.androidtv.R +import org.jellyfin.androidtv.auth.ui.validateAuthentication import org.jellyfin.androidtv.data.service.BackgroundService import org.jellyfin.androidtv.ui.navigation.Destinations import org.jellyfin.androidtv.ui.navigation.NavigationRepository @@ -28,6 +29,8 @@ class NextUpActivity : FragmentActivity(R.layout.fragment_content_view) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (!validateAuthentication()) return + applyTheme() val useExternalPlayer = intent.getBooleanExtra(EXTRA_USE_EXTERNAL_PLAYER, false) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/shared/AbstractActivityLifecycleCallbacks.kt b/app/src/main/java/org/jellyfin/androidtv/ui/shared/AbstractActivityLifecycleCallbacks.kt deleted file mode 100644 index 51220cd9b7..0000000000 --- a/app/src/main/java/org/jellyfin/androidtv/ui/shared/AbstractActivityLifecycleCallbacks.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.jellyfin.androidtv.ui.shared - -import android.app.Activity -import android.app.Application -import android.os.Bundle - -/** - * Abstract class to avoid defining all members of the [Application.ActivityLifecycleCallbacks] - * interface on usages. - * - * @see Application.ActivityLifecycleCallbacks - */ -abstract class AbstractActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks { - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit - override fun onActivityStarted(activity: Activity) = Unit - override fun onActivityResumed(activity: Activity) = Unit - override fun onActivityPaused(activity: Activity) = Unit - override fun onActivityStopped(activity: Activity) = Unit - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit - override fun onActivityDestroyed(activity: Activity) = Unit -} diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/shared/AuthenticatedUserCallbacks.kt b/app/src/main/java/org/jellyfin/androidtv/ui/shared/AuthenticatedUserCallbacks.kt deleted file mode 100644 index d74525b9ff..0000000000 --- a/app/src/main/java/org/jellyfin/androidtv/ui/shared/AuthenticatedUserCallbacks.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.jellyfin.androidtv.ui.shared - -import android.app.Activity -import android.content.Intent -import android.os.Build -import android.os.Bundle -import org.jellyfin.androidtv.auth.repository.SessionRepository -import org.jellyfin.androidtv.ui.preference.PreferencesActivity -import org.jellyfin.androidtv.ui.startup.StartupActivity -import timber.log.Timber - -/** - * ActivityLifecycleCallback that bounces to the StartupActivity when an activity is created and - * the currentUser is null. - */ -class AuthenticatedUserCallbacks( - private val sessionRepository: SessionRepository, -) : AbstractActivityLifecycleCallbacks() { - companion object { - val ignoredClassNames = arrayOf( - // Startup activities - StartupActivity::class.qualifiedName, - PreferencesActivity::class.qualifiedName, - // Screensaver activity is not exposed in Android SDK - "android.service.dreams.DreamActivity" - ) - } - - override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) activity.checkAuthentication() - } - - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) activity.checkAuthentication() - } - - private fun Activity.checkAuthentication() { - val name = this::class.qualifiedName - - if (name in ignoredClassNames) { - Timber.i("Activity $name is ignored") - } else if (sessionRepository.currentSession.value == null) { - Timber.w("Activity $name started without a session, bouncing to StartupActivity") - startActivity(Intent(this, StartupActivity::class.java)) - finish() - } - } -}