Skip to content

Commit

Permalink
Add multiple alarms management
Browse files Browse the repository at this point in the history
  • Loading branch information
gdelataillade committed Oct 26, 2023
1 parent f439fa0 commit 9436aeb
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ class AlarmPlugin: FlutterPlugin, MethodCallHandler {
"setAlarm" -> {
val alarmIntent = Intent(context, AlarmReceiver::class.java)

val id = call.argument<Int>("id")
val delayInSeconds = call.argument<Int>("delayInSeconds")

alarmIntent.putExtra("id", call.argument<Int>("id"))
alarmIntent.putExtra("id", id)
alarmIntent.putExtra("assetAudioPath", call.argument<String>("assetAudioPath"))
alarmIntent.putExtra("loopAudio", call.argument<Boolean>("loopAudio"))
alarmIntent.putExtra("vibrate", call.argument<Boolean>("vibrate"))
Expand All @@ -39,14 +40,22 @@ class AlarmPlugin: FlutterPlugin, MethodCallHandler {
alarmIntent.putExtra("notificationBody", call.argument<String>("notificationBody"))

val triggerAtMillis = System.currentTimeMillis() + delayInSeconds!! * 1000
val pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0)
val pendingIntent = PendingIntent.getBroadcast(context, id!!, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

Log.d("AlarmService", "triggerAtMillis: $triggerAtMillis")
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent)

result.success(true)
}
"stopAlarm" -> {
val id = call.argument<Int>("id")
val stopIntent = Intent(context, AlarmService::class.java)
stopIntent.action = "STOP_ALARM"
stopIntent.putExtra("id", id)
context.startService(stopIntent)
result.success(true)
}
"setNotificationOnKillService" -> {
val title = call.argument<String>("title")
val description = call.argument<String>("description")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package com.gdelataillade.alarm.alarm

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.app.PendingIntent
import android.app.*
import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.media.AudioManager
import android.media.AudioManager.FLAG_SHOW_UI
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.*
import androidx.core.app.NotificationCompat
import io.flutter.Log
import kotlin.math.round

class AlarmService : Service() {
private var mediaPlayer: MediaPlayer? = null
private val mediaPlayers = mutableMapOf<Int, MediaPlayer>()
private var vibrator: Vibrator? = null
private val CHANNEL_ID = "AlarmServiceChannel"

override fun onCreate() {
Expand All @@ -30,11 +24,18 @@ class AlarmService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("AlarmService", "onStartCommand")

val id = intent?.getIntExtra("id", 0)
val action = intent?.action
val id = intent?.getIntExtra("id", 0) ?: 0

if (action == "STOP_ALARM" && id != -1) {
stopAlarm(id)
return START_NOT_STICKY
}

val assetAudioPath = intent?.getStringExtra("assetAudioPath")
val loopAudio = intent?.getBooleanExtra("loopAudio", true)
val vibrate = intent?.getBooleanExtra("vibrate", true)
val volume = intent?.getDoubleExtra("volume", -1.0)
val volume = intent?.getDoubleExtra("volume", -1.0) ?: -1.0
val fadeDuration = intent?.getDoubleExtra("fadeDuration", 0.0)
val notificationTitle = intent?.getStringExtra("notificationTitle")
val notificationBody = intent?.getStringExtra("notificationBody")
Expand Down Expand Up @@ -65,15 +66,22 @@ class AlarmService : Service() {
startForeground(id!!, notification)
}

val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager

if (volume != -1.0) {
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
///
var maxVolume:Int = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
var _volume:Int = (round(volume!! * maxVolume)).toInt()
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val _volume = (round(volume * maxVolume)).toInt()

audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, _volume, if (showSystemUI) FLAG_SHOW_UI else 0)
}

// Request audio focus
val focusRequestResult = audioManager.requestAudioFocus(
null,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
)

try {
val assetManager = applicationContext.assets
val descriptor = assetManager.openFd("flutter_assets/" + assetAudioPath!!)
Expand All @@ -85,28 +93,52 @@ class AlarmService : Service() {
}
mediaPlayer.start()

// Store MediaPlayer instance in map
mediaPlayers[id] = mediaPlayer

} catch (e: Exception) {
// Handle exceptions related to asset loading or MediaPlayer
e.printStackTrace()
}

if (vibrate!!) {
// Obtain Vibrator instance if not already obtained
if (vibrator == null) {
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
}

// Vibrate the device in a loop: vibrate for 500ms, pause for 500ms
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
val pattern = longArrayOf(0, 500, 500) // Start immediately, vibrate 500ms, pause 500ms
val repeat = 1 // Repeat from the second element (0-based) of the pattern, which is the pause
val vibrationEffect = VibrationEffect.createWaveform(pattern, repeat)
vibrator.vibrate(vibrationEffect)
vibrator?.vibrate(vibrationEffect)
}

// Wake up the device
val wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager)
.newWakeLock(PowerManager.FULL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, "app:AlarmWakelockTag")
wakeLock.acquire(5 * 60 * 1000L /*5 minutes*/)

return START_STICKY
}

fun stopAlarm(id: Int) {
mediaPlayers[id]?.stop()
mediaPlayers[id]?.release()
mediaPlayers.remove(id)

// Abandon audio focus
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.abandonAudioFocus(null)

// Check if there are no more active alarms
if (mediaPlayers.isEmpty()) {
vibrator?.cancel()
stopForeground(true)
stopSelf()
}
}

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
Expand All @@ -121,8 +153,12 @@ class AlarmService : Service() {
}

override fun onDestroy() {
mediaPlayer?.stop()
mediaPlayer?.release()
mediaPlayers.values.forEach {
it.stop()
it.release()
}
mediaPlayers.clear()
vibrator?.cancel()
super.onDestroy()
}

Expand Down
23 changes: 4 additions & 19 deletions lib/src/android_alarm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class AndroidAlarm {
'assetAudioPath': settings.assetAudioPath,
'loopAudio': settings.loopAudio,
'vibrate': settings.vibrate,
'volume': 1.0,
'volume': -1.0,
// 'volume': settings.volumeMax ? 1.0 : -1.0,
'fadeDuration': settings.fadeDuration,
'notificationTitle': settings.notificationTitle,
Expand Down Expand Up @@ -269,24 +269,9 @@ class AndroidAlarm {
/// Sends the message `stop` to the isolate so the audio player
/// can stop playing and dispose.
static Future<bool> stop(int id) async {
ringing = false;
vibrationsActive = false;

final send = IsolateNameServer.lookupPortByName(stopPort);

if (send != null) {
send.send('stop');
alarmPrint('Alarm with id $id stopped');
}

if (previousVolume != null) {
VolumeController().setVolume(previousVolume!, showSystemUI: true);
previousVolume = null;
}

if (!hasOtherAlarms) stopNotificationOnKillService();

return await AndroidAlarmManager.cancel(id);
final res = await platform.invokeMethod('stopAlarm', {'id': id});
alarmPrint('[DEV] stopAlarm method channel invoked, returned: $res');
return res;
}

static Future<void> stopNotificationOnKillService() async {
Expand Down

0 comments on commit 9436aeb

Please sign in to comment.