diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 8d2759c..6f7d651 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -48,5 +48,9 @@
+
+
diff --git a/android/app/src/main/java/com/massive/AlarmModule.kt b/android/app/src/main/java/com/massive/AlarmModule.kt
index 8b252be..b405bfb 100644
--- a/android/app/src/main/java/com/massive/AlarmModule.kt
+++ b/android/app/src/main/java/com/massive/AlarmModule.kt
@@ -104,15 +104,14 @@ class AlarmModule(context: ReactApplicationContext?) :
@ReactMethod
fun timer(milliseconds: Int, description: String) {
Log.d("AlarmModule", "Queue alarm for $milliseconds delay")
- currentDescription = description
- val manager = getManager()
- manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
- val intent = Intent(reactApplicationContext, AlarmService::class.java)
- reactApplicationContext.stopService(intent)
- countdownTimer?.cancel()
- countdownTimer = getTimer(milliseconds)
- countdownTimer?.start()
- running = true
+ val intent = Intent(reactApplicationContext, TimerService::class.java)
+ intent.putExtra("milliseconds", milliseconds)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ reactApplicationContext.startForegroundService(intent)
+ }
+ else {
+ reactApplicationContext.startService(intent)
+ }
}
private fun getTimer(
@@ -147,12 +146,7 @@ class AlarmModule(context: ReactApplicationContext?) :
override fun onFinish() {
val context = reactApplicationContext
val intent = Intent(context, AlarmService::class.java)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- context.startForegroundService(intent)
- }
- else {
- context.startService(intent)
- }
+ context.startService(intent)
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(
"tick",
diff --git a/android/app/src/main/java/com/massive/TimerService.kt b/android/app/src/main/java/com/massive/TimerService.kt
new file mode 100644
index 0000000..71207fb
--- /dev/null
+++ b/android/app/src/main/java/com/massive/TimerService.kt
@@ -0,0 +1,155 @@
+package com.massive
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.app.*
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.os.*
+import android.util.Log
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+
+class TimerService : Service() {
+
+ private lateinit var timerHandler: Handler
+ private var timerRunnable: Runnable? = null
+ private var timeLeftInSeconds: Int = 0
+ private var timeTotalInSeconds: Int = 0
+ private var notificationId = 1
+
+ private val stopReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ Log.d("TimerService", "Received stop broadcast intent")
+ stopSelf()
+ }
+ }
+
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ override fun onCreate() {
+ super.onCreate()
+ timerHandler = Handler(Looper.getMainLooper())
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ applicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST),
+ Context.RECEIVER_NOT_EXPORTED)
+ }
+ else {
+ applicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
+ }
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ timeLeftInSeconds = (intent?.getIntExtra("milliseconds", 0) ?: 0) / 1000
+ startForeground(notificationId, createNotification(timeLeftInSeconds))
+ Log.d("TimerService", "onStartCommand seconds=$timeLeftInSeconds")
+ timeTotalInSeconds = timeLeftInSeconds
+
+ timerRunnable = object : Runnable {
+ override fun run() {
+ if (timeLeftInSeconds > 0) {
+ timeLeftInSeconds--
+ updateNotification(timeLeftInSeconds)
+ timerHandler.postDelayed(this, 1000)
+ } else {
+ startAlarmService()
+ stopSelf()
+ }
+ }
+ }
+ timerHandler.postDelayed(timerRunnable!!, 1000)
+ return START_STICKY
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ timerHandler.removeCallbacks(timerRunnable!!)
+ applicationContext.unregisterReceiver(stopReceiver)
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+
+ private fun createNotification(timeLeftInSeconds: Int): Notification {
+ val notificationTitle = "Timer"
+ val notificationText = formatTime(timeLeftInSeconds)
+ val notificationChannelId = "timer_channel"
+ val notificationIntent = Intent(this, TimerService::class.java)
+ val pendingIntent = PendingIntent.getActivity(
+ this,
+ 0,
+ notificationIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ val stopBroadcast = Intent(AlarmModule.STOP_BROADCAST)
+ stopBroadcast.setPackage(applicationContext.packageName)
+ val pendingStop =
+ PendingIntent.getBroadcast(applicationContext, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE)
+
+ val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId)
+ .setContentTitle(notificationTitle)
+ .setContentText(notificationText)
+ .setSmallIcon(R.drawable.ic_baseline_timer_24)
+ .setProgress(timeTotalInSeconds, timeLeftInSeconds, false)
+ .setContentIntent(pendingIntent)
+ .setCategory(NotificationCompat.CATEGORY_PROGRESS)
+ .setAutoCancel(false)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setOngoing(true)
+ .setDeleteIntent(pendingStop)
+ .addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop)
+
+ val notificationManager = NotificationManagerCompat.from(this)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ notificationChannelId,
+ "Timer Channel",
+ NotificationManager.IMPORTANCE_LOW
+ )
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ return notificationBuilder.build()
+ }
+
+ private fun updateNotification(timeLeftInSeconds: Int) {
+ val notificationManager = NotificationManagerCompat.from(this)
+ val notification = createNotification(timeLeftInSeconds)
+ if (ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ // TODO: Consider calling
+ // ActivityCompat#requestPermissions
+ // here to request the missing permissions, and then overriding
+ // public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ // int[] grantResults)
+ // to handle the case where the user grants the permission. See the documentation
+ // for ActivityCompat#requestPermissions for more details.
+ return
+ }
+ notificationManager.notify(notificationId, notification)
+ }
+
+ private fun formatTime(timeInSeconds: Int): String {
+ val minutes = timeInSeconds / 60
+ val seconds = timeInSeconds % 60
+ return String.format("%02d:%02d", minutes, seconds)
+ }
+
+ private fun startAlarmService() {
+ val intent = Intent(applicationContext, AlarmService::class.java)
+ applicationContext.startService(intent)
+ }
+
+ companion object {
+ const val STOP_BROADCAST = "stop-timer-event"
+ }
+}