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:
parent
327c5c28fe
commit
8b7b2c26c0
3 changed files with 7 additions and 85 deletions
|
|
@ -3,8 +3,7 @@ package com.example.helios_alarm_clock.receiver
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.PowerManager
|
import com.example.helios_alarm_clock.service.AlarmRingService
|
||||||
import com.example.helios_alarm_clock.ui.AlarmActivity
|
|
||||||
|
|
||||||
class AlarmReceiver : BroadcastReceiver() {
|
class AlarmReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
|
@ -12,23 +11,9 @@ class AlarmReceiver : BroadcastReceiver() {
|
||||||
val alarmId = intent.getStringExtra(EXTRA_ALARM_ID) ?: return
|
val alarmId = intent.getStringExtra(EXTRA_ALARM_ID) ?: return
|
||||||
val label = intent.getStringExtra(EXTRA_ALARM_LABEL) ?: "Alarm"
|
val label = intent.getStringExtra(EXTRA_ALARM_LABEL) ?: "Alarm"
|
||||||
|
|
||||||
// Wake lock to keep CPU alive while we launch the activity
|
// Start foreground service with full-screen intent notification.
|
||||||
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
// Direct startActivity() from a BroadcastReceiver is blocked on Android 10+.
|
||||||
val wl = pm.newWakeLock(
|
AlarmRingService.start(context, alarmId, label)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ import android.media.AudioAttributes
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.VibrationEffect
|
|
||||||
import android.os.Vibrator
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.example.helios_alarm_clock.HeliosApp
|
import com.example.helios_alarm_clock.HeliosApp
|
||||||
|
|
@ -32,7 +30,6 @@ class AlarmRingService : Service() {
|
||||||
@Inject lateinit var alarmDao: AlarmDao
|
@Inject lateinit var alarmDao: AlarmDao
|
||||||
|
|
||||||
private var mediaPlayer: MediaPlayer? = null
|
private var mediaPlayer: MediaPlayer? = null
|
||||||
private var vibrator: Vibrator? = null
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
|
@ -57,7 +54,6 @@ class AlarmRingService : Service() {
|
||||||
}
|
}
|
||||||
|
|
||||||
startSound()
|
startSound()
|
||||||
startVibration()
|
|
||||||
|
|
||||||
// Delete the fired alarm from the database
|
// Delete the fired alarm from the database
|
||||||
if (alarmId.isNotEmpty()) {
|
if (alarmId.isNotEmpty()) {
|
||||||
|
|
@ -86,8 +82,6 @@ class AlarmRingService : Service() {
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {}
|
||||||
}
|
}
|
||||||
mediaPlayer = null
|
mediaPlayer = null
|
||||||
vibrator?.cancel()
|
|
||||||
vibrator = null
|
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
super.onDestroy()
|
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 {
|
private fun buildNotification(alarmId: String, label: String): android.app.Notification {
|
||||||
val fullScreenIntent = Intent(this, AlarmActivity::class.java).apply {
|
val fullScreenIntent = Intent(this, AlarmActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
package com.example.helios_alarm_clock.ui
|
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.Bundle
|
||||||
import android.os.VibrationEffect
|
|
||||||
import android.os.Vibrator
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.ComponentActivity
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.helios_alarm_clock.data.AlarmDao
|
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 com.example.helios_alarm_clock.ui.theme.HeliosTheme
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -45,8 +41,6 @@ class AlarmActivity : ComponentActivity() {
|
||||||
|
|
||||||
@Inject lateinit var alarmDao: AlarmDao
|
@Inject lateinit var alarmDao: AlarmDao
|
||||||
|
|
||||||
private var mediaPlayer: MediaPlayer? = null
|
|
||||||
private var vibrator: Vibrator? = null
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -60,8 +54,7 @@ class AlarmActivity : ComponentActivity() {
|
||||||
val alarmId = intent.getStringExtra(EXTRA_ALARM_ID) ?: ""
|
val alarmId = intent.getStringExtra(EXTRA_ALARM_ID) ?: ""
|
||||||
val label = intent.getStringExtra(EXTRA_ALARM_LABEL) ?: "Alarm"
|
val label = intent.getStringExtra(EXTRA_ALARM_LABEL) ?: "Alarm"
|
||||||
|
|
||||||
startSound()
|
// Sound + vibration are handled by AlarmRingService
|
||||||
startVibration()
|
|
||||||
|
|
||||||
// Delete the fired alarm from the database
|
// Delete the fired alarm from the database
|
||||||
if (alarmId.isNotEmpty()) {
|
if (alarmId.isNotEmpty()) {
|
||||||
|
|
@ -91,47 +84,7 @@ class AlarmActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopAlarm() {
|
private fun stopAlarm() {
|
||||||
mediaPlayer?.let {
|
AlarmRingService.stop(this)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue