├── automotive ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── styles.xml │ │ │ ├── colors.xml │ │ │ ├── themes.xml │ │ │ ├── arrays.xml │ │ │ └── strings.xml │ │ ├── xml │ │ │ ├── automotive_app_desc.xml │ │ │ ├── authenticator.xml │ │ │ └── preferences.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ └── ic_launcher_foreground.webp │ │ ├── layout │ │ │ ├── activity_sign_in.xml │ │ │ ├── activity_settings.xml │ │ │ ├── server_sign_in.xml │ │ │ └── username_password_sign_in.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── drawable │ │ │ ├── playlists.xml │ │ │ ├── star_filled.xml │ │ │ ├── schedule.xml │ │ │ ├── app_logo.xml │ │ │ └── casino.xml │ │ ├── app_logo-playstore.png │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ └── be │ │ │ └── bendardenne │ │ │ └── jellyfin │ │ │ └── aaos │ │ │ ├── Constants.kt │ │ │ ├── SharkMarmaladeApplication.kt │ │ │ ├── auth │ │ │ ├── AuthenticatorService.kt │ │ │ └── Authenticator.kt │ │ │ ├── settings │ │ │ ├── SettingsFragmentViewModel.kt │ │ │ ├── SettingsFragment.kt │ │ │ └── SettingsActivity.kt │ │ │ ├── JellyfinHiltModule.kt │ │ │ ├── JellyfinAccountManager.kt │ │ │ ├── signin │ │ │ ├── SignInActivity.kt │ │ │ ├── ServerSignInFragment.kt │ │ │ ├── UsernamePasswordSignInFragment.kt │ │ │ └── SignInActivityViewModel.kt │ │ │ ├── CommandButtons.kt │ │ │ ├── AlbumArtContentProvider.kt │ │ │ ├── JellyfinMusicService.kt │ │ │ ├── JellyfinMediaTree.kt │ │ │ ├── MediaItemFactory.kt │ │ │ └── JellyfinMediaLibrarySessionCallback.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle.kts ├── assets ├── playstore │ ├── banner.png │ ├── tablet.png │ ├── portrait.png │ ├── portrait2.png │ ├── landscape1.png │ ├── landscape2.png │ └── landscape3_honda.png └── play_store_512x512.png ├── settings.gradle.kts ├── .gitignore ├── README.md ├── gradle.properties └── LICENSE /automotive/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /automotive/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/playstore/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/assets/playstore/banner.png -------------------------------------------------------------------------------- /assets/playstore/tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/assets/playstore/tablet.png -------------------------------------------------------------------------------- /assets/play_store_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/assets/play_store_512x512.png -------------------------------------------------------------------------------- /assets/playstore/portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/assets/playstore/portrait.png -------------------------------------------------------------------------------- /assets/playstore/portrait2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/assets/playstore/portrait2.png -------------------------------------------------------------------------------- /assets/playstore/landscape1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/assets/playstore/landscape1.png -------------------------------------------------------------------------------- /assets/playstore/landscape2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/assets/playstore/landscape2.png -------------------------------------------------------------------------------- /assets/playstore/landscape3_honda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/assets/playstore/landscape3_honda.png -------------------------------------------------------------------------------- /automotive/src/main/app_logo-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/app_logo-playstore.png -------------------------------------------------------------------------------- /automotive/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /automotive/src/main/res/xml/automotive_app_desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/Constants.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos 2 | 3 | object Constants { 4 | const val LOG_MARKER = "SharkMarmalade" 5 | } -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /automotive/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #93A3AC 4 | -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendardenne/sharkmarmalade/HEAD/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /automotive/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/SharkMarmaladeApplication.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class SharkMarmaladeApplication : Application() { 8 | } -------------------------------------------------------------------------------- /automotive/src/main/res/layout/activity_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /automotive/src/main/res/xml/authenticator.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /automotive/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = "Shark Marmalade" 17 | include(":automotive") 18 | -------------------------------------------------------------------------------- /automotive/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #93A3AC 4 | #667278 5 | #daeaea 6 | #0e1011 7 | 8 | #DADADA 9 | 10 | #fff1f3f4 11 | #ffdadce0 12 | -------------------------------------------------------------------------------- /automotive/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/auth/AuthenticatorService.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos.auth 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.os.IBinder 6 | 7 | class AuthenticatorService : Service() { 8 | private lateinit var authenticator: Authenticator 9 | 10 | override fun onCreate() { 11 | super.onCreate() 12 | authenticator = Authenticator(this) 13 | } 14 | 15 | override fun onBind(intent: Intent): IBinder { 16 | return authenticator.iBinder 17 | } 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Log/OS Files 9 | *.log 10 | 11 | # Android Studio generated files and folders 12 | captures/ 13 | .externalNativeBuild/ 14 | .cxx/ 15 | *.apk 16 | output.json 17 | 18 | # IntelliJ 19 | *.iml 20 | .idea/ 21 | misc.xml 22 | deploymentTargetDropDown.xml 23 | render.experimental.xml 24 | 25 | # Keystore files 26 | *.jks 27 | *.keystore 28 | 29 | # Google Services (e.g. APIs or Firebase) 30 | google-services.json 31 | 32 | # Android Profiling 33 | *.hprof 34 | -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/settings/SettingsFragmentViewModel.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos.settings 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.hilt.android.lifecycle.HiltViewModel 5 | import org.jellyfin.sdk.Jellyfin 6 | import javax.inject.Inject 7 | 8 | @HiltViewModel 9 | class SettingsFragmentViewModel @Inject constructor() : ViewModel() { 10 | 11 | @Inject 12 | lateinit var jellyfin: Jellyfin 13 | 14 | fun versionString(): CharSequence = 15 | "SharkMarmalade: ${jellyfin.clientInfo?.version}, Jellyfin API: ${Jellyfin.apiVersion}" 16 | } -------------------------------------------------------------------------------- /automotive/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Direct stream 5 | 320 kbps 6 | 256 kbps 7 | 192 kbps 8 | 160 kbps 9 | 128 kbps 10 | 11 | 12 | 13 | Direct stream 14 | 320000 15 | 256000 16 | 192000 17 | 160000 18 | 128000 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shark Marmalade 2 | 3 | ![Shark Marmalade UI in a Polestar 2](https://www.bendardenne.be/img/sharkmarmelade.png) 4 | 5 | Shark Marmalade is a third-party music client for Jellyfin servers, targetting Android Automotive (AAOS). 6 | 7 | 8 | Jellyfin on Google Play 9 | 10 | 11 | ## Known compatible cars 12 | 13 | Please feel free to add to this list if you manage to use the app in your car! 14 | 15 | - Polestar 2 16 | - Polestar 3 17 | - Volvo XC90 18 | - Volvo XC60 19 | - Volvo XC40 20 | - Volvo EX30 21 | - Chevy Equinox EV 22 | - Renault / Nissan ?? OpenR Link 23 | -------------------------------------------------------------------------------- /automotive/src/main/res/drawable/playlists.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | -------------------------------------------------------------------------------- /automotive/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /automotive/src/main/res/drawable/star_filled.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /automotive/proguard-rules.pro: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/settings/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos.settings 2 | 3 | import android.os.Bundle 4 | import androidx.lifecycle.ViewModelProvider 5 | import androidx.preference.Preference 6 | import androidx.preference.PreferenceFragmentCompat 7 | import be.bendardenne.jellyfin.aaos.R 8 | import dagger.hilt.android.AndroidEntryPoint 9 | 10 | @AndroidEntryPoint 11 | class SettingsFragment : PreferenceFragmentCompat() { 12 | 13 | private lateinit var viewModel: SettingsFragmentViewModel 14 | 15 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 16 | setPreferencesFromResource(R.xml.preferences, rootKey) 17 | 18 | viewModel = ViewModelProvider(this)[SettingsFragmentViewModel::class.java] 19 | findPreference("version")?.summary = viewModel.versionString() 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /automotive/src/main/res/drawable/schedule.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /automotive/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 13 | 14 | 20 | 21 | 22 | 23 | 28 | 29 | -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/JellyfinHiltModule.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos 2 | 3 | import android.accounts.AccountManager 4 | import android.content.Context 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import org.jellyfin.sdk.Jellyfin 11 | import org.jellyfin.sdk.android.androidDevice 12 | import org.jellyfin.sdk.createJellyfin 13 | import org.jellyfin.sdk.model.ClientInfo 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | class JellyfinHiltModule { 18 | 19 | @Provides 20 | fun provideJellyfin(@ApplicationContext appContext: Context): Jellyfin { 21 | val version = 22 | appContext.packageManager.getPackageInfo(appContext.packageName, 0).versionName 23 | 24 | return createJellyfin { 25 | clientInfo = ClientInfo(appContext.getString(R.string.app_name), version ?: "unknown") 26 | deviceInfo = androidDevice(appContext) 27 | context = appContext 28 | } 29 | } 30 | 31 | @Provides 32 | fun provideAccountManager(@ApplicationContext appContext: Context): JellyfinAccountManager { 33 | return JellyfinAccountManager(AccountManager.get(appContext)) 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos.settings 2 | 3 | import android.os.Bundle 4 | import androidx.activity.OnBackPressedCallback 5 | import androidx.appcompat.app.AppCompatActivity 6 | import be.bendardenne.jellyfin.aaos.R 7 | import be.bendardenne.jellyfin.aaos.databinding.ActivitySettingsBinding 8 | import dagger.hilt.android.AndroidEntryPoint 9 | 10 | 11 | @AndroidEntryPoint 12 | class SettingsActivity : AppCompatActivity() { 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | val binding = ActivitySettingsBinding.inflate(layoutInflater) 17 | setContentView(binding.root) 18 | 19 | setSupportActionBar(binding.toolbar) 20 | supportActionBar?.setHomeButtonEnabled(true) 21 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 22 | supportActionBar?.setTitle(R.string.settings) 23 | 24 | supportFragmentManager 25 | .beginTransaction() 26 | .replace(R.id.settings_container, SettingsFragment()) 27 | .commit() 28 | 29 | onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) { 30 | override fun handleOnBackPressed() = finish() 31 | }) 32 | } 33 | 34 | override fun onSupportNavigateUp(): Boolean { 35 | onBackPressedDispatcher.onBackPressed() 36 | return true 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /automotive/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Shark Marmalade 3 | Sign in to your Jellyfin server 4 | https://myjellyfin.server.com:8096 5 | Connect to server 6 | Server URL cannot be empty 7 | QuickConnect 8 | QuickConnect succeeded 9 | Open Jellyfin on your phone,\n select QuickConnect,\n and enter this code. 10 | Error 11 | Unavailable 12 | username 13 | password 14 | Log In 15 | Username cannot be empty 16 | Could not reach server 17 | Failed to login 18 | Artists 19 | Tracks 20 | Albums 21 | Playlists 22 | Settings 23 | Maximum bitrate 24 | -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/JellyfinAccountManager.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos 2 | 3 | import android.accounts.Account 4 | import android.accounts.AccountManager 5 | import android.os.Bundle 6 | import be.bendardenne.jellyfin.aaos.auth.Authenticator 7 | 8 | class JellyfinAccountManager(private val accountManager: AccountManager) { 9 | 10 | companion object { 11 | const val ACCOUNT_TYPE = Authenticator.ACCOUNT_TYPE 12 | const val TOKEN_TYPE = "$ACCOUNT_TYPE.access_token" 13 | const val USERDATA_SERVER_KEY = "$ACCOUNT_TYPE.server" 14 | } 15 | 16 | private val account: Account? 17 | get() = accountManager.getAccountsByType(ACCOUNT_TYPE).firstOrNull() 18 | 19 | val server: String? 20 | get() = account?.let { accountManager.getUserData(it, USERDATA_SERVER_KEY) } 21 | 22 | val token: String? 23 | get() = account?.let { accountManager.peekAuthToken(it, TOKEN_TYPE) } 24 | 25 | val isAuthenticated: Boolean 26 | get() = token != null 27 | 28 | fun storeAccount(server: String, username: String, token: String): Account { 29 | // Find existing account, if any 30 | var account = accountManager.getAccountsByType(ACCOUNT_TYPE).firstOrNull { 31 | accountManager.getUserData(it, USERDATA_SERVER_KEY).equals(server) && 32 | it.name.equals(username) 33 | } 34 | 35 | if (account == null) { 36 | account = Account(username, ACCOUNT_TYPE) 37 | accountManager.addAccountExplicitly( 38 | account, 39 | "", // We don't keep the password, just the auth token. 40 | Bundle().also { it.putString(USERDATA_SERVER_KEY, server) } 41 | ) 42 | } 43 | 44 | accountManager.setAuthToken(account, TOKEN_TYPE, token) 45 | 46 | return account 47 | } 48 | } -------------------------------------------------------------------------------- /automotive/src/main/res/drawable/app_logo.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/signin/SignInActivity.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos.signin 2 | 3 | import android.content.ComponentName 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.concurrent.futures.await 7 | import androidx.lifecycle.ViewModelProvider 8 | import androidx.lifecycle.lifecycleScope 9 | import androidx.media3.session.MediaController 10 | import androidx.media3.session.SessionCommand 11 | import androidx.media3.session.SessionToken 12 | import be.bendardenne.jellyfin.aaos.JellyfinMediaLibrarySessionCallback.Companion.LOGIN_COMMAND 13 | import be.bendardenne.jellyfin.aaos.JellyfinMusicService 14 | import be.bendardenne.jellyfin.aaos.R 15 | import dagger.hilt.android.AndroidEntryPoint 16 | import kotlinx.coroutines.launch 17 | 18 | 19 | @AndroidEntryPoint 20 | class SignInActivity : AppCompatActivity() { 21 | 22 | private lateinit var viewModel: SignInActivityViewModel 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | setContentView(R.layout.activity_sign_in) 27 | 28 | viewModel = ViewModelProvider(this)[SignInActivityViewModel::class.java] 29 | 30 | viewModel.loggedIn.observe(this) { loggedIn -> 31 | if (loggedIn == true) { 32 | val service = ComponentName(applicationContext, JellyfinMusicService::class.java) 33 | val future = MediaController.Builder( 34 | applicationContext, 35 | SessionToken(applicationContext, service) 36 | ).buildAsync() 37 | 38 | lifecycleScope.launch { 39 | val controller = future.await() 40 | controller.sendCustomCommand(SessionCommand(LOGIN_COMMAND, Bundle()), Bundle()) 41 | finish() 42 | } 43 | 44 | } 45 | } 46 | 47 | supportFragmentManager.beginTransaction() 48 | .add(R.id.sign_in_container, ServerSignInFragment()) 49 | .commit() 50 | } 51 | } -------------------------------------------------------------------------------- /automotive/src/main/java/be/bendardenne/jellyfin/aaos/CommandButtons.kt: -------------------------------------------------------------------------------- 1 | package be.bendardenne.jellyfin.aaos 2 | 3 | import android.os.Bundle 4 | import androidx.annotation.OptIn 5 | import androidx.media3.common.Player 6 | import androidx.media3.common.Player.REPEAT_MODE_ALL 7 | import androidx.media3.common.Player.REPEAT_MODE_OFF 8 | import androidx.media3.common.Player.REPEAT_MODE_ONE 9 | import androidx.media3.common.util.UnstableApi 10 | import androidx.media3.session.CommandButton 11 | import androidx.media3.session.SessionCommand 12 | import be.bendardenne.jellyfin.aaos.JellyfinMediaLibrarySessionCallback.Companion.REPEAT_COMMAND 13 | import be.bendardenne.jellyfin.aaos.JellyfinMediaLibrarySessionCallback.Companion.SHUFFLE_COMMAND 14 | import com.google.common.collect.ImmutableList 15 | 16 | /** 17 | * Helper for creating the custom command buttons we need for a player. 18 | */ 19 | object CommandButtons { 20 | 21 | @OptIn(UnstableApi::class) 22 | fun createButtons(player: Player): List { 23 | // The UI should show the icon for the active mode 24 | val repeatIcon = when (player.repeatMode) { 25 | REPEAT_MODE_ALL -> CommandButton.ICON_REPEAT_ALL 26 | REPEAT_MODE_OFF -> CommandButton.ICON_REPEAT_OFF 27 | REPEAT_MODE_ONE -> CommandButton.ICON_REPEAT_ONE 28 | else -> throw IllegalStateException("Unexpected change to Repeat mode") 29 | } 30 | 31 | val repeat = 32 | CommandButton.Builder(repeatIcon) 33 | .setDisplayName("Toggle repeat") 34 | .setSessionCommand(SessionCommand(REPEAT_COMMAND, Bundle.EMPTY)) 35 | .setSlots(CommandButton.SLOT_OVERFLOW) 36 | .build() 37 | 38 | val shuffleIcon = 39 | if (player.shuffleModeEnabled) 40 | CommandButton.ICON_SHUFFLE_ON 41 | else 42 | CommandButton.ICON_SHUFFLE_OFF 43 | 44 | val shuffle = CommandButton.Builder(shuffleIcon) 45 | .setDisplayName("Toggle Shuffle") 46 | .setSessionCommand(SessionCommand(SHUFFLE_COMMAND, Bundle.EMPTY)) 47 | .setSlots(CommandButton.SLOT_OVERFLOW) 48 | .build() 49 | 50 | return ImmutableList.of(shuffle, repeat) 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /automotive/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | id("kotlin-kapt") 5 | id("com.google.dagger.hilt.android") 6 | } 7 | 8 | android { 9 | namespace = "be.bendardenne.jellyfin.aaos" 10 | compileSdk = 35 11 | 12 | defaultConfig { 13 | applicationId = "be.bendardenne.jellyfin.aaos" 14 | minSdk = 29 15 | targetSdk = 34 16 | versionCode = 26 17 | versionName = "1.1" 18 | 19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 20 | } 21 | 22 | buildFeatures { 23 | dataBinding = true 24 | viewBinding = true 25 | } 26 | 27 | buildTypes { 28 | release { 29 | isMinifyEnabled = false 30 | proguardFiles( 31 | getDefaultProguardFile("proguard-android-optimize.txt"), 32 | "proguard-rules.pro" 33 | ) 34 | } 35 | } 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_1_8 38 | targetCompatibility = JavaVersion.VERSION_1_8 39 | } 40 | kotlinOptions { 41 | jvmTarget = "1.8" 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation("androidx.car:car:1.0.0-alpha7") 47 | implementation("androidx.core:core-ktx:1.16.0") 48 | implementation("androidx.constraintlayout:constraintlayout:2.2.1") 49 | implementation("androidx.preference:preference-ktx:1.2.1") 50 | implementation("androidx.concurrent:concurrent-futures-ktx:1.2.0") 51 | implementation("androidx.credentials:credentials:1.5.0") 52 | implementation("androidx.databinding:databinding-runtime:8.10.0") 53 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.0") 54 | implementation("androidx.media3:media3-exoplayer:1.7.1") 55 | implementation("androidx.media3:media3-session:1.7.1") 56 | implementation("androidx.media3:media3-ui:1.7.1") 57 | implementation("com.squareup.okhttp3:okhttp:4.12.0") 58 | implementation("org.jellyfin.sdk:jellyfin-core:1.6.1") 59 | implementation("com.google.dagger:hilt-android:2.51.1") 60 | kapt("com.google.dagger:hilt-compiler:2.51.1") 61 | 62 | testImplementation("junit:junit:4.13.2") 63 | androidTestImplementation("androidx.test.ext:junit:1.2.1") 64 | androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") 65 | } -------------------------------------------------------------------------------- /automotive/src/main/res/drawable/casino.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /automotive/src/main/res/layout/server_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 19 | 20 | 34 | 35 | 43 | 44 |