Twelve: Add offload toggle

Co-authored-by: Luca Stefani <luca.stefani.ge1@gmail.com>
Change-Id: I8fc8f233a87721f732507df3759fe063168a1e85
diff --git a/app/src/main/java/org/lineageos/twelve/SettingsActivity.kt b/app/src/main/java/org/lineageos/twelve/SettingsActivity.kt
index 13d9a2a..2f1bec9 100644
--- a/app/src/main/java/org/lineageos/twelve/SettingsActivity.kt
+++ b/app/src/main/java/org/lineageos/twelve/SettingsActivity.kt
@@ -19,10 +19,16 @@
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
 import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreference
 import com.google.android.material.appbar.AppBarLayout
 import com.google.android.material.appbar.MaterialToolbar
+import kotlinx.coroutines.launch
+import org.lineageos.twelve.ext.ENABLE_OFFLOAD_KEY
 import org.lineageos.twelve.ext.setOffset
+import org.lineageos.twelve.viewmodels.SettingsViewModel
 import kotlin.reflect.safeCast
 
 class SettingsActivity : AppCompatActivity(R.layout.activity_settings) {
@@ -64,6 +70,9 @@
     abstract class SettingsFragment(
         @XmlRes private val preferencesResId: Int,
     ) : PreferenceFragmentCompat() {
+        // View model
+        protected val viewModel by viewModels<SettingsViewModel>()
+
         private val settingsActivity
             get() = SettingsActivity::class.safeCast(activity)
 
@@ -130,5 +139,19 @@
         }
     }
 
-    class RootSettingsFragment : SettingsFragment(R.xml.root_preferences)
+    class RootSettingsFragment : SettingsFragment(R.xml.root_preferences) {
+        // Preferences
+        private val enableOffload by lazy { findPreference<SwitchPreference>(ENABLE_OFFLOAD_KEY)!! }
+
+        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+            super.onCreatePreferences(savedInstanceState, rootKey)
+
+            enableOffload.setOnPreferenceChangeListener { _, newValue ->
+                lifecycleScope.launch {
+                    viewModel.toggleOffload(newValue as Boolean)
+                }
+                true
+            }
+        }
+    }
 }
diff --git a/app/src/main/java/org/lineageos/twelve/ext/Player.kt b/app/src/main/java/org/lineageos/twelve/ext/Player.kt
index 0a711d0..61f41d0 100644
--- a/app/src/main/java/org/lineageos/twelve/ext/Player.kt
+++ b/app/src/main/java/org/lineageos/twelve/ext/Player.kt
@@ -11,6 +11,7 @@
 import androidx.media3.common.PlaybackParameters
 import androidx.media3.common.Player
 import androidx.media3.common.Timeline
+import androidx.media3.common.TrackSelectionParameters
 import androidx.media3.common.Tracks
 import androidx.media3.common.util.UnstableApi
 import kotlinx.coroutines.channels.awaitClose
@@ -210,3 +211,20 @@
     get() = (0 until mediaItemCount).map {
         getMediaItemAt(it)
     }
+
+@OptIn(UnstableApi::class)
+fun Player.setOffloadEnabled(enabled: Boolean) {
+    trackSelectionParameters = trackSelectionParameters.buildUpon()
+        .setAudioOffloadPreferences(
+            TrackSelectionParameters.AudioOffloadPreferences
+                .Builder()
+                .setAudioOffloadMode(
+                    if (enabled) {
+                        TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED
+                    } else {
+                        TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_DISABLED
+                    }
+                )
+                .build()
+        ).build()
+}
diff --git a/app/src/main/java/org/lineageos/twelve/ext/SharedPreferences.kt b/app/src/main/java/org/lineageos/twelve/ext/SharedPreferences.kt
new file mode 100644
index 0000000..b92ca66
--- /dev/null
+++ b/app/src/main/java/org/lineageos/twelve/ext/SharedPreferences.kt
@@ -0,0 +1,19 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.twelve.ext
+
+import android.content.SharedPreferences
+import androidx.core.content.edit
+
+// Generic prefs
+const val ENABLE_OFFLOAD_KEY = "enable_offload"
+private const val ENABLE_OFFLOAD_DEFAULT = true
+
+var SharedPreferences.enableOffload: Boolean
+    get() = getBoolean(ENABLE_OFFLOAD_KEY, ENABLE_OFFLOAD_DEFAULT)
+    set(value) = edit {
+        putBoolean(ENABLE_OFFLOAD_KEY, value)
+    }
diff --git a/app/src/main/java/org/lineageos/twelve/services/PlaybackService.kt b/app/src/main/java/org/lineageos/twelve/services/PlaybackService.kt
index ce62c73..99ecb37 100644
--- a/app/src/main/java/org/lineageos/twelve/services/PlaybackService.kt
+++ b/app/src/main/java/org/lineageos/twelve/services/PlaybackService.kt
@@ -19,7 +19,6 @@
 import androidx.media3.common.C
 import androidx.media3.common.MediaItem
 import androidx.media3.common.Player
-import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences
 import androidx.media3.common.util.UnstableApi
 import androidx.media3.exoplayer.ExoPlayer
 import androidx.media3.session.DefaultMediaNotificationProvider
@@ -27,11 +26,14 @@
 import androidx.media3.session.MediaLibraryService
 import androidx.media3.session.MediaSession
 import androidx.media3.session.SessionError
+import androidx.preference.PreferenceManager
 import kotlinx.coroutines.guava.future
 import kotlinx.coroutines.launch
 import org.lineageos.twelve.MainActivity
 import org.lineageos.twelve.R
 import org.lineageos.twelve.TwelveApplication
+import org.lineageos.twelve.ext.enableOffload
+import org.lineageos.twelve.ext.setOffloadEnabled
 import org.lineageos.twelve.ui.widgets.NowPlayingAppWidgetProvider
 
 @OptIn(UnstableApi::class)
@@ -49,6 +51,10 @@
         )
     }
 
+    private val sharedPreferences by lazy {
+        PreferenceManager.getDefaultSharedPreferences(this)
+    }
+
     private val resumptionPlaylistRepository by lazy {
         (application as TwelveApplication).resumptionPlaylistRepository
     }
@@ -196,14 +202,7 @@
 
         exoPlayer.addListener(this)
 
-        exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
-            .buildUpon()
-            .setAudioOffloadPreferences(
-                AudioOffloadPreferences
-                    .Builder()
-                    .setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED)
-                    .build()
-            ).build()
+        exoPlayer.setOffloadEnabled(sharedPreferences.enableOffload)
 
         mediaLibrarySession = MediaLibrarySession.Builder(
             this, exoPlayer, mediaLibrarySessionCallback
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/SettingsViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/SettingsViewModel.kt
new file mode 100644
index 0000000..bc42d6d
--- /dev/null
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/SettingsViewModel.kt
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.twelve.viewmodels
+
+import android.app.Application
+import android.content.ComponentName
+import androidx.annotation.OptIn
+import androidx.lifecycle.AndroidViewModel
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.session.MediaController
+import androidx.media3.session.SessionToken
+import kotlinx.coroutines.guava.await
+import org.lineageos.twelve.ext.applicationContext
+import org.lineageos.twelve.ext.setOffloadEnabled
+import org.lineageos.twelve.services.PlaybackService
+
+class SettingsViewModel(application: Application) : AndroidViewModel(application) {
+    private val sessionToken by lazy {
+        SessionToken(
+            applicationContext,
+            ComponentName(applicationContext, PlaybackService::class.java)
+        )
+    }
+
+    @OptIn(UnstableApi::class)
+    suspend fun toggleOffload(offload: Boolean) {
+        val mediaController = MediaController.Builder(applicationContext, sessionToken)
+            .buildAsync()
+            .await()
+
+        mediaController.setOffloadEnabled(offload)
+        mediaController.release()
+    }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 10b38aa..ae69f05 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -142,6 +142,13 @@
     <!-- Settings title -->
     <string name="title_activity_settings">Settings</string>
 
+    <!-- Preference titles -->
+    <string name="general_header">General</string>
+
+    <!-- General preferences -->
+    <string name="enable_offload">Enable offload</string>
+    <string name="enable_offload_summary">Use the offload audio path for audio playback. Disabling this may increase power usage but can be useful if you experience issues with audio playback or post processing</string>
+
     <!-- Now playing widget -->
     <string name="now_playing_widget_description">Now playing</string>
     <string name="now_playing_widget_dummy_title">Title</string>
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index d6118c0..4db2c1c 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -3,4 +3,18 @@
      SPDX-FileCopyrightText: 2024 The LineageOS Project
      SPDX-License-Identifier: Apache-2.0
 -->
-<PreferenceScreen />
+<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceCategory
+        app:iconSpaceReserved="false"
+        app:title="@string/general_header">
+
+        <SwitchPreference
+            app:defaultValue="true"
+            app:iconSpaceReserved="false"
+            app:key="enable_offload"
+            app:summary="@string/enable_offload_summary"
+            app:title="@string/enable_offload" />
+    </PreferenceCategory>
+
+</PreferenceScreen>