Add settings to disable battery optimizations and to make the player service unkillable
This commit is contained in:
@@ -6,6 +6,9 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import androidx.core.content.getSystemService
|
||||
|
||||
|
||||
inline fun <reified T> Context.intent(): Intent =
|
||||
Intent(this@Context, T::class.java)
|
||||
@@ -20,4 +23,11 @@ inline fun <reified T: Activity> Context.activityPendingIntent(
|
||||
requestCode: Int = 0,
|
||||
flags: Int = if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0,
|
||||
): PendingIntent =
|
||||
PendingIntent.getActivity(this, requestCode, intent<T>(), flags)
|
||||
PendingIntent.getActivity(this, requestCode, intent<T>(), flags)
|
||||
|
||||
val Context.isIgnoringBatteryOptimizations: Boolean
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
getSystemService<PowerManager>()?.isIgnoringBatteryOptimizations(packageName) ?: true
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
||||
|
||||
// https://stackoverflow.com/q/53502244/16885569
|
||||
// I found four ways to make the system not kill the stopped foreground service: e.g. when
|
||||
// the player is paused:
|
||||
// 1 - Use the solution below - hacky;
|
||||
// 2 - Do not call stopForeground but provide a button to dismiss the notification - bad UX;
|
||||
// 3 - Lower the targetSdk (e.g. to 23) - security concerns;
|
||||
// 4 - Host the service in a separate process - overkill and pathetic.
|
||||
abstract class InvincibleService : Service() {
|
||||
protected val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
protected abstract val isInvincibilityEnabled: Boolean
|
||||
|
||||
protected abstract val notificationId: Int
|
||||
|
||||
private var invincibility: Invincibility? = null
|
||||
|
||||
private val isAllowedToStartForegroundServices: Boolean
|
||||
get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.S || isIgnoringBatteryOptimizations
|
||||
|
||||
override fun onBind(intent: Intent?): Binder? {
|
||||
invincibility?.stop()
|
||||
invincibility = null
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onRebind(intent: Intent?) {
|
||||
invincibility?.stop()
|
||||
invincibility = null
|
||||
super.onRebind(intent)
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
if (isInvincibilityEnabled && isAllowedToStartForegroundServices) {
|
||||
invincibility = Invincibility()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
invincibility?.stop()
|
||||
invincibility = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
protected fun makeInvincible(isInvincible: Boolean = true) {
|
||||
if (isInvincible) {
|
||||
invincibility?.start()
|
||||
} else {
|
||||
invincibility?.stop()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun shouldBeInvincible(): Boolean
|
||||
|
||||
protected abstract fun notification(): Notification?
|
||||
|
||||
private inner class Invincibility : BroadcastReceiver(), Runnable {
|
||||
private var isStarted = false
|
||||
private val intervalMs = 30_000L
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_ON -> handler.post(this)
|
||||
Intent.ACTION_SCREEN_OFF -> notification()?.let { notification ->
|
||||
handler.removeCallbacks(this)
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun start() {
|
||||
if (!isStarted) {
|
||||
isStarted = true
|
||||
handler.postDelayed(this, intervalMs)
|
||||
registerReceiver(this, IntentFilter().apply {
|
||||
addAction(Intent.ACTION_SCREEN_ON)
|
||||
addAction(Intent.ACTION_SCREEN_OFF)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun stop() {
|
||||
if (isStarted) {
|
||||
handler.removeCallbacks(this)
|
||||
unregisterReceiver(this)
|
||||
isStarted = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
if (shouldBeInvincible() && isAllowedToStartForegroundServices) {
|
||||
notification()?.let { notification ->
|
||||
startForeground(notificationId, notification)
|
||||
stopForeground(false)
|
||||
handler.postDelayed(this, intervalMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ class Preferences(
|
||||
initialSkipSilence: Boolean,
|
||||
initialVolumeNormalization: Boolean,
|
||||
initialPersistentQueue: Boolean,
|
||||
initialIsInvincibilityEnabled: Boolean,
|
||||
) {
|
||||
constructor(preferences: SharedPreferences) : this(
|
||||
edit = { action: SharedPreferences.Editor.() -> Unit ->
|
||||
@@ -39,7 +40,8 @@ class Preferences(
|
||||
initialExoPlayerDiskCacheMaxSizeBytes = preferences.getLong(Keys.exoPlayerDiskCacheMaxSizeBytes, 512L * 1024 * 1024),
|
||||
initialSkipSilence = preferences.getBoolean(Keys.skipSilence, false),
|
||||
initialVolumeNormalization = preferences.getBoolean(Keys.volumeNormalization, false),
|
||||
initialPersistentQueue = preferences.getBoolean(Keys.persistentQueue, false)
|
||||
initialPersistentQueue = preferences.getBoolean(Keys.persistentQueue, false),
|
||||
initialIsInvincibilityEnabled = preferences.getBoolean(Keys.isInvincibilityEnabled, false),
|
||||
)
|
||||
|
||||
var songSortBy = initialSongSortBy
|
||||
@@ -75,6 +77,9 @@ class Preferences(
|
||||
var persistentQueue = initialPersistentQueue
|
||||
set(value) = edit { putBoolean(Keys.persistentQueue, value) }
|
||||
|
||||
var isInvincibilityEnabled = initialIsInvincibilityEnabled
|
||||
set(value) = edit { putBoolean(Keys.isInvincibilityEnabled, value) }
|
||||
|
||||
object Keys {
|
||||
const val songSortOrder = "songSortOrder"
|
||||
const val songSortBy = "songSortBy"
|
||||
@@ -87,6 +92,7 @@ class Preferences(
|
||||
const val skipSilence = "skipSilence"
|
||||
const val volumeNormalization = "volumeNormalization"
|
||||
const val persistentQueue = "persistentQueue"
|
||||
const val isInvincibilityEnabled = "isInvincibilityEnabled"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user