diff --git a/CHANGELOG.md b/CHANGELOG.md index a7580d0..a9cff47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,8 @@ The first release of Clockula: alarms, timers, a stopwatch and a world clock. default sound instead. - A ring screen over the lock screen with snooze and dismiss. The dismiss challenge always gives you a way out, works with a screen reader, and Back - can't silence an alarm. + can't silence an alarm. It also opens when the alarm or a timer goes off + while you're using Clockula. - Choose what the volume buttons do while an alarm rings: change the volume (as before), snooze, dismiss or nothing. Dismiss still respects the dismiss challenge by snoozing instead, and a snooze press with no snoozes left diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/alarm/ring/AlarmRingService.kt b/app/src/main/java/de/jeanlucmakiola/clockula/alarm/ring/AlarmRingService.kt index 1b18bd8..fb10699 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/alarm/ring/AlarmRingService.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/alarm/ring/AlarmRingService.kt @@ -18,8 +18,10 @@ import de.jeanlucmakiola.clockula.domain.alarm.RingPresentationPolicy import de.jeanlucmakiola.clockula.domain.alarm.RingSession import de.jeanlucmakiola.clockula.domain.ring.RingFallbackPolicy import de.jeanlucmakiola.clockula.domain.ring.SilenceAfter +import de.jeanlucmakiola.clockula.ring.ForegroundRingScreen import de.jeanlucmakiola.clockula.ring.RingAudioPlayer import de.jeanlucmakiola.clockula.ring.RingVibrator +import de.jeanlucmakiola.clockula.ui.ring.AlarmRingActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -128,10 +130,12 @@ class AlarmRingService : Service() { * replaces it with the alarm's own notification as soon as the session is * read. Same channel and same id, so the user sees one notification — and * the placeholder is the first post, so it is the one that opens the ring - * screen. + * screen. Inside the app the full-screen intent only shows a heads-up, + * so the screen is opened directly there. */ private fun startForegroundPreparing() { startForeground(RingNotifications.preparing(this, presentation(), repostPendingIntent())) + ForegroundRingScreen.launchIfVisible(this, AlarmRingActivity::class.java) } private fun startForeground(session: RingSession) { diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ring/ForegroundRingScreen.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ring/ForegroundRingScreen.kt new file mode 100644 index 0000000..e3b05d9 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ring/ForegroundRingScreen.kt @@ -0,0 +1,38 @@ +package de.jeanlucmakiola.clockula.ring + +import android.app.Activity +import android.app.ActivityManager +import android.content.Context +import android.content.Intent + +/** + * Opens a ring screen directly while one of Clockula's own windows is on + * screen. + * + * A full-screen intent opens its activity only when the phone is locked or the + * display is off. On a phone in use, and so always while the user is inside + * Clockula, SystemUI shows it as a heads-up and the ring screen never comes up. + * An app with a visible window may start activities from the background, so + * the ring services start the screen themselves in that one case. Everywhere + * else the full-screen intent stays in charge, and a heads-up over another app + * is what the platform asks for. + */ +object ForegroundRingScreen { + + fun launchIfVisible(context: Context, screen: Class) { + if (!appIsVisible()) return + // A refused start costs nothing: the notification is up either way. + runCatching { + context.startActivity( + Intent(context, screen).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK), + ) + } + } + + /** The process holds a visible activity, not only a foreground service. */ + private fun appIsVisible(): Boolean { + val process = ActivityManager.RunningAppProcessInfo() + ActivityManager.getMyMemoryState(process) + return process.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE + } +} diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/timer/service/TimerService.kt b/app/src/main/java/de/jeanlucmakiola/clockula/timer/service/TimerService.kt index 7ed553a..e3f7048 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/timer/service/TimerService.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/timer/service/TimerService.kt @@ -20,12 +20,14 @@ import de.jeanlucmakiola.clockula.domain.timer.TimerMode import de.jeanlucmakiola.clockula.domain.timer.TimerRing import de.jeanlucmakiola.clockula.domain.timer.TimerRingDecision import de.jeanlucmakiola.clockula.domain.timer.TimerSettings +import de.jeanlucmakiola.clockula.ring.ForegroundRingScreen import de.jeanlucmakiola.clockula.ring.RingAudioPlayer import de.jeanlucmakiola.clockula.ring.RingVibrator import de.jeanlucmakiola.clockula.timer.AlarmRingStatus import de.jeanlucmakiola.clockula.timer.TimerEngine import de.jeanlucmakiola.clockula.timer.TimerIntents import de.jeanlucmakiola.clockula.timer.TimerServiceState +import de.jeanlucmakiola.clockula.ui.ring.TimerRingActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -286,6 +288,8 @@ class TimerService : Service() { fullScreen = sounding && capabilities.snapshot().canUseFullScreenIntent } startForeground(TimerNotifications.timer(this, state, fullScreen)) + // Inside the app the full-screen intent only shows a heads-up. + if (!state.alertOnce && sounding) ForegroundRingScreen.launchIfVisible(this, TimerRingActivity::class.java) } /**