diff --git a/android/app/src/main/java/com/massive/AlarmModule.kt b/android/app/src/main/java/com/massive/AlarmModule.kt index 89d0b37..509f62f 100644 --- a/android/app/src/main/java/com/massive/AlarmModule.kt +++ b/android/app/src/main/java/com/massive/AlarmModule.kt @@ -63,7 +63,7 @@ class AlarmModule constructor(context: ReactApplicationContext?) : countdownTimer?.start() running = true val manager = getManager() - manager.cancel(NOTIFICATION_ID_DONE) + manager.cancel(AlarmService.NOTIFICATION_ID_DONE) val intent = Intent(reactApplicationContext, AlarmService::class.java) reactApplicationContext.stopService(intent) } @@ -77,7 +77,7 @@ class AlarmModule constructor(context: ReactApplicationContext?) : val intent = Intent(reactApplicationContext, AlarmService::class.java) reactApplicationContext?.stopService(intent) val manager = getManager() - manager.cancel(NOTIFICATION_ID_DONE) + manager.cancel(AlarmService.NOTIFICATION_ID_DONE) manager.cancel(NOTIFICATION_ID_PENDING) val params = Arguments.createMap().apply { putString("minutes", "00") @@ -93,7 +93,7 @@ class AlarmModule constructor(context: ReactApplicationContext?) : fun timer(milliseconds: Int) { Log.d("AlarmModule", "Queue alarm for $milliseconds delay") val manager = getManager() - manager.cancel(NOTIFICATION_ID_DONE) + manager.cancel(AlarmService.NOTIFICATION_ID_DONE) val intent = Intent(reactApplicationContext, AlarmService::class.java) reactApplicationContext.stopService(intent) countdownTimer?.cancel() @@ -133,25 +133,8 @@ class AlarmModule constructor(context: ReactApplicationContext?) : @RequiresApi(Build.VERSION_CODES.O) override fun onFinish() { val context = reactApplicationContext - val finishIntent = Intent(context, StopAlarm::class.java) - val finishPending = PendingIntent.getActivity( - context, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE - ) - val fullIntent = Intent(context, TimerDone::class.java) - val fullPending = PendingIntent.getActivity( - context, 0, fullIntent, PendingIntent.FLAG_IMMUTABLE - ) - builder.setContentText("Timer finished.").setProgress(0, 0, false) - .setAutoCancel(true).setOngoing(true).setFullScreenIntent(fullPending, true) - .setContentIntent(finishPending).setChannelId(CHANNEL_ID_DONE) - .setCategory(NotificationCompat.CATEGORY_ALARM).priority = - NotificationCompat.PRIORITY_HIGH - val manager = getManager() - manager.notify(NOTIFICATION_ID_DONE, builder.build()) - manager.cancel(NOTIFICATION_ID_PENDING) - val alarmIntent = Intent(context, AlarmService::class.java) - context.startService(alarmIntent) - reactApplicationContext + context.startForegroundService(Intent(context, AlarmService::class.java)) + context .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("finish", Arguments.createMap().apply { putString("minutes", "00") @@ -169,12 +152,12 @@ class AlarmModule constructor(context: ReactApplicationContext?) : val pendingContent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE) val addBroadcast = Intent(ADD_BROADCAST).apply { - setPackage(reactApplicationContext.packageName) + setPackage(context.packageName) } val pendingAdd = PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE) val stopBroadcast = Intent(STOP_BROADCAST) - stopBroadcast.setPackage(reactApplicationContext.packageName) + stopBroadcast.setPackage(context.packageName) val pendingStop = PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE) return NotificationCompat.Builder(context, CHANNEL_ID_PENDING) @@ -187,16 +170,9 @@ class AlarmModule constructor(context: ReactApplicationContext?) : @RequiresApi(Build.VERSION_CODES.O) private fun getManager(): NotificationManager { - val alarmsChannel = NotificationChannel( - CHANNEL_ID_DONE, CHANNEL_ID_DONE, NotificationManager.IMPORTANCE_HIGH - ) - alarmsChannel.description = "Alarms for rest timers." - alarmsChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC - alarmsChannel.setSound(null, null) val notificationManager = reactApplicationContext.getSystemService( NotificationManager::class.java ) - notificationManager.createNotificationChannel(alarmsChannel) val timersChannel = NotificationChannel( CHANNEL_ID_PENDING, CHANNEL_ID_PENDING, NotificationManager.IMPORTANCE_LOW ) @@ -210,8 +186,6 @@ class AlarmModule constructor(context: ReactApplicationContext?) : const val STOP_BROADCAST = "stop-timer-event" const val ADD_BROADCAST = "add-timer-event" const val CHANNEL_ID_PENDING = "Timer" - const val CHANNEL_ID_DONE = "Alarm" const val NOTIFICATION_ID_PENDING = 1 - const val NOTIFICATION_ID_DONE = 2 } } diff --git a/android/app/src/main/java/com/massive/AlarmService.kt b/android/app/src/main/java/com/massive/AlarmService.kt index b54b49a..beb7d57 100644 --- a/android/app/src/main/java/com/massive/AlarmService.kt +++ b/android/app/src/main/java/com/massive/AlarmService.kt @@ -1,7 +1,7 @@ package com.massive import android.annotation.SuppressLint -import android.app.Service +import android.app.* import android.content.Context import android.content.Intent import android.media.AudioAttributes @@ -9,42 +9,57 @@ import android.media.MediaPlayer import android.media.MediaPlayer.OnPreparedListener import android.net.Uri import android.os.* -import android.util.Log import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean) + +@RequiresApi(Build.VERSION_CODES.O) class AlarmService : Service(), OnPreparedListener { private var mediaPlayer: MediaPlayer? = null private var vibrator: Vibrator? = null - @SuppressLint("Recycle") - @RequiresApi(api = Build.VERSION_CODES.O) - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - if (intent.action == "stop") { - onDestroy() - return START_STICKY + private fun getBuilder(): NotificationCompat.Builder { + val context = applicationContext + val contentIntent = Intent(context, MainActivity::class.java) + val pendingContent = + PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE) + val addBroadcast = Intent(AlarmModule.ADD_BROADCAST).apply { + setPackage(context.packageName) } + val pendingAdd = + PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE) + val stopBroadcast = Intent(AlarmModule.STOP_BROADCAST) + stopBroadcast.setPackage(context.packageName) + val pendingStop = + PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE) + return NotificationCompat.Builder(context, AlarmModule.CHANNEL_ID_PENDING) + .setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24).setContentTitle("Resting") + .setContentIntent(pendingContent) + .addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop) + .addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd) + .setDeleteIntent(pendingStop) + } + + @SuppressLint("Range") + private fun getSettings(): Settings { val db = DatabaseHelper(applicationContext).readableDatabase + val cursor = db.rawQuery("SELECT sound, noSound, vibrate FROM settings", null) + cursor.moveToFirst() + val sound = cursor.getString(cursor.getColumnIndex("sound")) + val noSound = cursor.getInt(cursor.getColumnIndex("noSound")) == 1 + val vibrate = cursor.getInt(cursor.getColumnIndex("vibrate")) == 1 + cursor.close() + return Settings(sound, noSound, vibrate) + } - val sound = db.rawQuery("SELECT sound FROM settings", null) - .let { - it.moveToFirst() - it.getString(0) - } - Log.d("AlarmService", "sound=$sound") - - val noSound = db.rawQuery("SELECT noSound FROM settings", null) - .let { - it.moveToFirst() - it.getInt(0) == 1 - } - Log.d("AlarmService", "noSound=$noSound") - - if (sound == null && !noSound) { + private fun playSound(settings: Settings) { + if (settings.sound == null && !settings.noSound) { mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon) mediaPlayer?.start() mediaPlayer?.setOnCompletionListener { vibrator?.cancel() } - } else if (sound != null && !noSound) { + } else if (settings.sound != null && !settings.noSound) { mediaPlayer = MediaPlayer().apply { setAudioAttributes( AudioAttributes.Builder() @@ -52,20 +67,56 @@ class AlarmService : Service(), OnPreparedListener { .setUsage(AudioAttributes.USAGE_MEDIA) .build() ) - setDataSource(applicationContext, Uri.parse(sound)) + setDataSource(applicationContext, Uri.parse(settings.sound)) prepare() start() setOnCompletionListener { vibrator?.cancel() } } } + } - val vibrate = db.rawQuery("SELECT vibrate FROM settings", null) - .let { - it.moveToFirst() - it.getInt(0) == 1 - } - if (!vibrate) return START_STICKY + private fun doNotify(): Notification { + val alarmsChannel = NotificationChannel( + CHANNEL_ID_DONE, + CHANNEL_ID_DONE, + NotificationManager.IMPORTANCE_HIGH + ) + alarmsChannel.description = "Alarms for rest timers." + alarmsChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC + alarmsChannel.setSound(null, null) + val manager = applicationContext.getSystemService( + NotificationManager::class.java + ) + manager.createNotificationChannel(alarmsChannel) + val builder = getBuilder() + val context = applicationContext + val finishIntent = Intent(context, StopAlarm::class.java) + val finishPending = PendingIntent.getActivity( + context, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE + ) + val fullIntent = Intent(context, TimerDone::class.java) + val fullPending = PendingIntent.getActivity( + context, 0, fullIntent, PendingIntent.FLAG_IMMUTABLE + ) + builder.setContentText("Timer finished.").setProgress(0, 0, false) + .setAutoCancel(true).setOngoing(true).setFullScreenIntent(fullPending, true) + .setContentIntent(finishPending).setChannelId(CHANNEL_ID_DONE) + .setCategory(NotificationCompat.CATEGORY_ALARM).priority = + NotificationCompat.PRIORITY_HIGH + val notification = builder.build() + manager.notify(NOTIFICATION_ID_DONE, notification) + manager.cancel(AlarmModule.NOTIFICATION_ID_PENDING) + return notification + } + @SuppressLint("Recycle") + @RequiresApi(api = Build.VERSION_CODES.O) + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + val notification = doNotify() + startForeground(NOTIFICATION_ID_DONE, notification) + val settings = getSettings() + playSound(settings) + if (!settings.vibrate) return START_STICKY val pattern = longArrayOf(0, 300, 1300, 300, 1300, 300) vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val vibratorManager = @@ -97,4 +148,9 @@ class AlarmService : Service(), OnPreparedListener { mediaPlayer?.release() vibrator?.cancel() } + + companion object { + const val CHANNEL_ID_DONE = "Alarm" + const val NOTIFICATION_ID_DONE = 2 + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/TimerDone.kt b/android/app/src/main/java/com/massive/TimerDone.kt index b247851..92027c1 100644 --- a/android/app/src/main/java/com/massive/TimerDone.kt +++ b/android/app/src/main/java/com/massive/TimerDone.kt @@ -23,7 +23,7 @@ class TimerDone : AppCompatActivity() { Log.d("TimerDone", "Stopping...") applicationContext.stopService(Intent(applicationContext, AlarmService::class.java)) val manager = getManager() - manager.cancel(AlarmModule.NOTIFICATION_ID_DONE) + manager.cancel(AlarmService.NOTIFICATION_ID_DONE) manager.cancel(AlarmModule.NOTIFICATION_ID_PENDING) val intent = Intent(applicationContext, MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK @@ -33,8 +33,8 @@ class TimerDone : AppCompatActivity() { @RequiresApi(Build.VERSION_CODES.O) fun getManager(): NotificationManager { val alarmsChannel = NotificationChannel( - AlarmModule.CHANNEL_ID_DONE, - AlarmModule.CHANNEL_ID_DONE, + AlarmService.CHANNEL_ID_DONE, + AlarmService.CHANNEL_ID_DONE, NotificationManager.IMPORTANCE_HIGH ).apply { description = "Alarms for rest timers."