mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 09:49:25 +02:00
Improve music playback stopping
This commit is contained in:
parent
9a8ccc193f
commit
bc12c5e2b4
8 changed files with 126 additions and 35 deletions
|
@ -45,7 +45,8 @@ abstract class PlatformIntegration(
|
|||
|
||||
abstract fun showAppLockScreen(currentPackageName: String, currentActivityName: String?)
|
||||
abstract fun showAnnoyScreen(annoyDuration: Long)
|
||||
abstract fun muteAudioIfPossible(packageName: String)
|
||||
// true = success
|
||||
abstract suspend fun muteAudioIfPossible(packageName: String): Boolean
|
||||
abstract fun setShowBlockingOverlay(show: Boolean, blockedElement: String? = null)
|
||||
// this should throw an SecurityException if the permission is missing
|
||||
abstract suspend fun getForegroundApps(queryInterval: Long, enableMultiAppDetection: Boolean): Set<ForegroundApp>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -25,6 +25,8 @@ import android.content.*
|
|||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.AudioManager
|
||||
import android.media.session.MediaController
|
||||
import android.media.session.MediaSessionManager
|
||||
import android.media.session.PlaybackState
|
||||
import android.os.Build
|
||||
|
@ -52,6 +54,8 @@ import io.timelimit.android.ui.manipulation.AnnoyActivity
|
|||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.consumeEach
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
|
@ -84,6 +88,7 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
|||
private val overlay = OverlayUtil(context as Application)
|
||||
private val battery = BatteryStatusUtil(context)
|
||||
private val connectedNetwork = ConnectedNetworkUtil(context)
|
||||
private val muteAudioMutex = Mutex()
|
||||
|
||||
init {
|
||||
AppsChangeListener.registerBroadcastReceiver(this.context, object : BroadcastReceiver() {
|
||||
|
@ -151,11 +156,7 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
|||
val manager = context.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager
|
||||
val sessions = manager.getActiveSessions(ComponentName(context, NotificationListener::class.java))
|
||||
|
||||
return sessions.find {
|
||||
it.playbackState?.state == PlaybackState.STATE_PLAYING ||
|
||||
it.playbackState?.state == PlaybackState.STATE_FAST_FORWARDING ||
|
||||
it.playbackState?.state == PlaybackState.STATE_REWINDING
|
||||
}?.packageName
|
||||
return sessions.find { isPlaying(it) }?.packageName
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,24 +271,89 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
|||
AnnoyActivity.start(context, annoyDuration)
|
||||
}
|
||||
|
||||
override fun muteAudioIfPossible(packageName: String) {
|
||||
override suspend fun muteAudioIfPossible(packageName: String): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (getNotificationAccessPermissionStatus() == NewPermissionStatus.Granted) {
|
||||
val manager = context.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager
|
||||
val sessions = manager.getActiveSessions(ComponentName(context, NotificationListener::class.java))
|
||||
val sessionsOfTheApp = sessions.filter { it.packageName == packageName }
|
||||
sessionsOfTheApp.forEach { session ->
|
||||
session.dispatchMediaButtonEvent(KeyEvent(
|
||||
KeyEvent.ACTION_DOWN,
|
||||
KeyEvent.KEYCODE_MEDIA_STOP
|
||||
))
|
||||
session.dispatchMediaButtonEvent(KeyEvent(
|
||||
KeyEvent.ACTION_UP,
|
||||
KeyEvent.KEYCODE_MEDIA_STOP
|
||||
))
|
||||
muteAudioMutex.withLock {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOG_TAG, "muteAudioIfPossible($packageName)")
|
||||
}
|
||||
|
||||
val manager = context.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager
|
||||
|
||||
fun getAppSessions(): List<MediaController> {
|
||||
return manager.getActiveSessions(ComponentName(context, NotificationListener::class.java))
|
||||
.filter { it.packageName == packageName }
|
||||
}
|
||||
|
||||
fun dispatchKey(sessions: List<MediaController>, key: Int) {
|
||||
sessions.forEach {
|
||||
it.dispatchMediaButtonEvent(KeyEvent(
|
||||
KeyEvent.ACTION_DOWN,
|
||||
key
|
||||
))
|
||||
it.dispatchMediaButtonEvent(KeyEvent(
|
||||
KeyEvent.ACTION_UP,
|
||||
key
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
kotlin.run {
|
||||
val sessions = getAppSessions()
|
||||
|
||||
if (sessions.find { isPlaying(it) } == null) return true
|
||||
|
||||
if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "try KEYCODE_MEDIA_STOP") }
|
||||
dispatchKey(sessions, KeyEvent.KEYCODE_MEDIA_STOP)
|
||||
}
|
||||
|
||||
delay(100)
|
||||
|
||||
kotlin.run {
|
||||
val sessions = getAppSessions()
|
||||
|
||||
if (sessions.find { isPlaying(it) } == null) return true
|
||||
|
||||
if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "try KEYCODE_HEADSETHOOK") }
|
||||
dispatchKey(sessions, KeyEvent.KEYCODE_HEADSETHOOK)
|
||||
}
|
||||
|
||||
delay(500)
|
||||
|
||||
kotlin.run {
|
||||
val sessions = getAppSessions()
|
||||
|
||||
if (sessions.find { isPlaying(it) } == null) return true
|
||||
|
||||
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
val listener = AudioManager.OnAudioFocusChangeListener {/* ignored */}
|
||||
|
||||
if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "try audio focus") }
|
||||
if (
|
||||
audioManager.requestAudioFocus(listener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
||||
) {
|
||||
if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "got audio focus") }
|
||||
delay(100)
|
||||
|
||||
audioManager.abandonAudioFocus(listener)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin.run {
|
||||
val sessions = getAppSessions()
|
||||
|
||||
if (sessions.find { isPlaying(it) } == null) return true
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "playback still running") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setShowBlockingOverlay(show: Boolean, blockedElement: String?) {
|
||||
|
@ -535,4 +601,10 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
|||
}
|
||||
|
||||
override fun getCurrentNetworkId(): NetworkId = connectedNetwork.getNetworkId()
|
||||
|
||||
private fun isPlaying(session: MediaController): Boolean {
|
||||
return session.playbackState?.state == PlaybackState.STATE_PLAYING ||
|
||||
session.playbackState?.state == PlaybackState.STATE_FAST_FORWARDING ||
|
||||
session.playbackState?.state == PlaybackState.STATE_REWINDING
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,8 +67,6 @@ class NotificationListener: NotificationListenerService() {
|
|||
lastOngoingNotificationHidden.remove(sbn.packageName)
|
||||
}
|
||||
} else {
|
||||
appLogic.platformIntegration.muteAudioIfPossible(sbn.packageName)
|
||||
|
||||
val success = try {
|
||||
if (sbn.isOngoing && SUPPORTS_HIDING_ONGOING_NOTIFICATIONS) {
|
||||
// only snooze for 5 seconds to show it again soon
|
||||
|
|
|
@ -98,8 +98,8 @@ class DummyIntegration(
|
|||
// ignore
|
||||
}
|
||||
|
||||
override fun muteAudioIfPossible(packageName: String) {
|
||||
// ignore
|
||||
override suspend fun muteAudioIfPossible(packageName: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setShowBlockingOverlay(show: Boolean, blockedElement: String?) {
|
||||
|
|
|
@ -119,6 +119,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
private val usedTimeUpdateHelper = UsedTimeUpdateHelper(appLogic)
|
||||
private var previousMainLogicExecutionTime = 0
|
||||
private var previousMainLoopEndTime = 0L
|
||||
private var previousAudioPlaybackBlock: Pair<Long, String>? = null
|
||||
private val dayChangeTracker = DayChangeTracker(
|
||||
timeApi = appLogic.timeApi,
|
||||
longDuration = 1000 * 60 * 10 /* 10 minutes */
|
||||
|
@ -703,7 +704,31 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
}
|
||||
|
||||
if (blockAudioPlayback && audioPlaybackPackageName != null) {
|
||||
appLogic.platformIntegration.muteAudioIfPossible(audioPlaybackPackageName)
|
||||
val currentAudioBlockUptime = appLogic.timeApi.getCurrentUptimeInMillis()
|
||||
val oldAudioPlaybackBlock = previousAudioPlaybackBlock
|
||||
val skipAudioBlock = oldAudioPlaybackBlock != null &&
|
||||
oldAudioPlaybackBlock.second == audioPlaybackPackageName &&
|
||||
oldAudioPlaybackBlock.first >= currentAudioBlockUptime
|
||||
|
||||
if (!skipAudioBlock) {
|
||||
val newAudioPlaybackBlock = currentAudioBlockUptime + 1000 * 10 /* block for 10 seconds */ to audioPlaybackPackageName
|
||||
|
||||
previousAudioPlaybackBlock = newAudioPlaybackBlock
|
||||
|
||||
runAsync {
|
||||
if (appLogic.platformIntegration.muteAudioIfPossible(audioPlaybackPackageName)) {
|
||||
appLogic.platformIntegration.showOverlayMessage(appLogic.context.getString(R.string.background_logic_toast_block_audio))
|
||||
|
||||
// allow blocking again
|
||||
// no locking needed because everything happens on the main thread
|
||||
if (previousAudioPlaybackBlock === newAudioPlaybackBlock) {
|
||||
previousAudioPlaybackBlock = appLogic.timeApi.getCurrentUptimeInMillis() + 1000 * 1 /* block for 1 more second */ to audioPlaybackPackageName
|
||||
}
|
||||
} else {
|
||||
appLogic.platformIntegration.showOverlayMessage(appLogic.context.getString(R.string.background_logic_toast_block_audio_failed))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex: SecurityException) {
|
||||
// this is handled by an other main loop (with a delay)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -90,10 +90,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder {
|
|||
|
||||
setContentView(R.layout.lock_activity)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
stopMediaPlayback()
|
||||
}
|
||||
|
||||
syncModel.statusText.observe(this) { supportActionBar?.subtitle = it }
|
||||
|
||||
currentInstances.add(this)
|
||||
|
@ -198,11 +194,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder {
|
|||
}
|
||||
}
|
||||
|
||||
private fun stopMediaPlayback() {
|
||||
val platformIntegration = DefaultAppLogic.with(this).platformIntegration
|
||||
platformIntegration.muteAudioIfPossible(blockedPackageName)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
// do nothing because going back would open the blocked app again
|
||||
// super.onBackPressed()
|
||||
|
|
|
@ -173,6 +173,8 @@
|
|||
<string name="background_logic_paused_text">Keine Einschränkungen</string>
|
||||
|
||||
<string name="background_logic_toast_sync_apps">TimeLimit: Fehler beim Aktualisieren der App-Liste</string>
|
||||
<string name="background_logic_toast_block_audio">TimeLimit: Musikwiedergabe beendet</string>
|
||||
<string name="background_logic_toast_block_audio_failed">TimeLimit: Anhalten der Musikwiedergabe fehlgeschlagen</string>
|
||||
|
||||
<string name="blocked_time_areas">
|
||||
Sperrzeiten
|
||||
|
|
|
@ -216,6 +216,8 @@
|
|||
<string name="background_logic_paused_text">No limitations apply</string>
|
||||
|
||||
<string name="background_logic_toast_sync_apps">TimeLimit: Failure while trying to update the app list</string>
|
||||
<string name="background_logic_toast_block_audio">TimeLimit: Music playback stopped</string>
|
||||
<string name="background_logic_toast_block_audio_failed">TimeLimit: Failed to stop music playback</string>
|
||||
|
||||
<string name="blocked_time_areas">
|
||||
Blocked time areas
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue