Fix alarm not ringing by using AlarmRingService with full-screen intent

AlarmReceiver was calling startActivity() directly, which Android 10+
blocks from background. Now delegates to AlarmRingService which uses a
foreground notification with full-screen intent. Removed duplicate
sound/vibration from AlarmActivity (service owns playback). Removed
all vibration code and VIBRATE permission.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Moritz 2026-02-15 16:54:37 +01:00
parent 327c5c28fe
commit 8b7b2c26c0
3 changed files with 7 additions and 85 deletions

View file

@ -3,8 +3,7 @@ package com.example.helios_alarm_clock.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.PowerManager
import com.example.helios_alarm_clock.ui.AlarmActivity
import com.example.helios_alarm_clock.service.AlarmRingService
class AlarmReceiver : BroadcastReceiver() {
@ -12,23 +11,9 @@ class AlarmReceiver : BroadcastReceiver() {
val alarmId = intent.getStringExtra(EXTRA_ALARM_ID) ?: return
val label = intent.getStringExtra(EXTRA_ALARM_LABEL) ?: "Alarm"
// Wake lock to keep CPU alive while we launch the activity
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val wl = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"helios::alarm_receiver"
)
wl.acquire(10_000L)
// Launch AlarmActivity directly — no notification
val activityIntent = Intent(context, AlarmActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NO_USER_ACTION
putExtra(EXTRA_ALARM_ID, alarmId)
putExtra(EXTRA_ALARM_LABEL, label)
}
context.startActivity(activityIntent)
// Start foreground service with full-screen intent notification.
// Direct startActivity() from a BroadcastReceiver is blocked on Android 10+.
AlarmRingService.start(context, alarmId, label)
}
companion object {

View file

@ -9,8 +9,6 @@ import android.media.AudioAttributes
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.os.IBinder
import android.os.VibrationEffect
import android.os.Vibrator
import android.util.Log
import androidx.core.app.NotificationCompat
import com.example.helios_alarm_clock.HeliosApp
@ -32,7 +30,6 @@ class AlarmRingService : Service() {
@Inject lateinit var alarmDao: AlarmDao
private var mediaPlayer: MediaPlayer? = null
private var vibrator: Vibrator? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -57,7 +54,6 @@ class AlarmRingService : Service() {
}
startSound()
startVibration()
// Delete the fired alarm from the database
if (alarmId.isNotEmpty()) {
@ -86,8 +82,6 @@ class AlarmRingService : Service() {
} catch (_: Exception) {}
}
mediaPlayer = null
vibrator?.cancel()
vibrator = null
scope.cancel()
super.onDestroy()
}
@ -116,16 +110,6 @@ class AlarmRingService : Service() {
}
}
private fun startVibration() {
try {
vibrator = getSystemService(Vibrator::class.java)
val pattern = longArrayOf(0, 800, 400, 800, 400)
vibrator?.vibrate(VibrationEffect.createWaveform(pattern, 0))
} catch (e: Exception) {
Log.e(TAG, "Failed to start vibration", e)
}
}
private fun buildNotification(alarmId: String, label: String): android.app.Notification {
val fullScreenIntent = Intent(this, AlarmActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP

View file

@ -1,11 +1,6 @@
package com.example.helios_alarm_clock.ui
import android.media.AudioAttributes
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator
import android.util.Log
import android.view.WindowManager
import androidx.activity.ComponentActivity
@ -30,6 +25,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.helios_alarm_clock.data.AlarmDao
import com.example.helios_alarm_clock.service.AlarmRingService
import com.example.helios_alarm_clock.ui.theme.HeliosTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
@ -45,8 +41,6 @@ class AlarmActivity : ComponentActivity() {
@Inject lateinit var alarmDao: AlarmDao
private var mediaPlayer: MediaPlayer? = null
private var vibrator: Vibrator? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun onCreate(savedInstanceState: Bundle?) {
@ -60,8 +54,7 @@ class AlarmActivity : ComponentActivity() {
val alarmId = intent.getStringExtra(EXTRA_ALARM_ID) ?: ""
val label = intent.getStringExtra(EXTRA_ALARM_LABEL) ?: "Alarm"
startSound()
startVibration()
// Sound + vibration are handled by AlarmRingService
// Delete the fired alarm from the database
if (alarmId.isNotEmpty()) {
@ -91,47 +84,7 @@ class AlarmActivity : ComponentActivity() {
}
private fun stopAlarm() {
mediaPlayer?.let {
try {
if (it.isPlaying) it.stop()
it.release()
} catch (_: Exception) {}
}
mediaPlayer = null
vibrator?.cancel()
vibrator = null
}
private fun startSound() {
try {
val alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
?: RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
?: return
mediaPlayer = MediaPlayer().apply {
setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
)
setDataSource(this@AlarmActivity, alarmUri)
isLooping = true
prepare()
start()
}
} catch (e: Exception) {
Log.e(TAG, "Failed to start alarm sound", e)
}
}
private fun startVibration() {
try {
vibrator = getSystemService(Vibrator::class.java)
val pattern = longArrayOf(0, 800, 400, 800, 400)
vibrator?.vibrate(VibrationEffect.createWaveform(pattern, 0))
} catch (e: Exception) {
Log.e(TAG, "Failed to start vibration", e)
}
AlarmRingService.stop(this)
}
companion object {