Use foreground service for alarm completion
Many of our errors in production are caused by the alarm module finishing. In devices after android version 7 we are "required" to use startForegroundService or else the following error supposedly occurs: Exception java.lang.IllegalStateException: at android.app.ContextImpl.startServiceCommon (ContextImpl.java:1725) at android.app.ContextImpl.startService (ContextImpl.java:1680) at android.content.ContextWrapper.startService (ContextWrapper.java:731) at android.content.ContextWrapper.startService (ContextWrapper.java:731) at com.massive.AlarmModule$getTimer$1.onFinish (AlarmModule.kt:144) at android.os.CountDownTimer$1.handleMessage (CountDownTimer.java:127) at android.os.Handler.dispatchMessage (Handler.java:106) at android.os.Looper.loop (Looper.java:236) at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run (MessageQueueThreadImpl.java:228) at java.lang.Thread.run (Thread.java:923) I say supposedly because on all of my testing devices (which are android 7+) this error doesn't occur.
This commit is contained in:
parent
1f6100607d
commit
07c704841d
|
@ -9,14 +9,14 @@ import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.facebook.react.bridge.*
|
import com.facebook.react.bridge.*
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
class AlarmModule constructor(context: ReactApplicationContext?) :
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
ReactContextBaseJavaModule(context) {
|
class AlarmModule(context: ReactApplicationContext?) :
|
||||||
|
ReactContextBaseJavaModule(context), LifecycleEventListener {
|
||||||
|
|
||||||
private var countdownTimer: CountDownTimer? = null
|
private var countdownTimer: CountDownTimer? = null
|
||||||
var currentMs: Long = 0
|
var currentMs: Long = 0
|
||||||
|
@ -29,7 +29,6 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
|
|
||||||
private val stopReceiver =
|
private val stopReceiver =
|
||||||
object : BroadcastReceiver() {
|
object : BroadcastReceiver() {
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
Log.d("AlarmModule", "Received stop broadcast intent")
|
Log.d("AlarmModule", "Received stop broadcast intent")
|
||||||
stop()
|
stop()
|
||||||
|
@ -38,24 +37,29 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
|
|
||||||
private val addReceiver =
|
private val addReceiver =
|
||||||
object : BroadcastReceiver() {
|
object : BroadcastReceiver() {
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
add()
|
add()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
reactApplicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST),
|
||||||
|
Context.RECEIVER_NOT_EXPORTED)
|
||||||
|
reactApplicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST),
|
||||||
|
Context.RECEIVER_NOT_EXPORTED)
|
||||||
|
}
|
||||||
|
else {
|
||||||
reactApplicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
|
reactApplicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
|
||||||
reactApplicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
|
reactApplicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCatalystInstanceDestroy() {
|
|
||||||
reactApplicationContext.unregisterReceiver(stopReceiver)
|
|
||||||
reactApplicationContext.unregisterReceiver(addReceiver)
|
|
||||||
super.onCatalystInstanceDestroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
override fun onHostDestroy() {
|
||||||
|
reactApplicationContext.unregisterReceiver(stopReceiver)
|
||||||
|
reactApplicationContext.unregisterReceiver(addReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
fun add() {
|
fun add() {
|
||||||
Log.d("AlarmModule", "Add 1 min to alarm.")
|
Log.d("AlarmModule", "Add 1 min to alarm.")
|
||||||
|
@ -77,7 +81,6 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
fun stop() {
|
fun stop() {
|
||||||
Log.d("AlarmModule", "Stop alarm.")
|
Log.d("AlarmModule", "Stop alarm.")
|
||||||
|
@ -98,7 +101,6 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
.emit("tick", params)
|
.emit("tick", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
fun timer(milliseconds: Int, description: String) {
|
fun timer(milliseconds: Int, description: String) {
|
||||||
Log.d("AlarmModule", "Queue alarm for $milliseconds delay")
|
Log.d("AlarmModule", "Queue alarm for $milliseconds delay")
|
||||||
|
@ -113,13 +115,11 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
running = true
|
running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
|
||||||
private fun getTimer(
|
private fun getTimer(
|
||||||
endMs: Int,
|
endMs: Int,
|
||||||
): CountDownTimer {
|
): CountDownTimer {
|
||||||
val builder = getBuilder()
|
val builder = getBuilder()
|
||||||
return object : CountDownTimer(endMs.toLong(), 1000) {
|
return object : CountDownTimer(endMs.toLong(), 1000) {
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
override fun onTick(current: Long) {
|
override fun onTick(current: Long) {
|
||||||
currentMs = current
|
currentMs = current
|
||||||
val seconds =
|
val seconds =
|
||||||
|
@ -144,10 +144,15 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
.emit("tick", params)
|
.emit("tick", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
val context = reactApplicationContext
|
val context = reactApplicationContext
|
||||||
context.startService(Intent(context, AlarmService::class.java))
|
val intent = Intent(context, AlarmService::class.java)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.startForegroundService(intent)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.startService(intent)
|
||||||
|
}
|
||||||
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||||
.emit(
|
.emit(
|
||||||
"tick",
|
"tick",
|
||||||
|
@ -161,7 +166,6 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnspecifiedImmutableFlag")
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
|
||||||
private fun getBuilder(): NotificationCompat.Builder {
|
private fun getBuilder(): NotificationCompat.Builder {
|
||||||
val context = reactApplicationContext
|
val context = reactApplicationContext
|
||||||
val contentIntent = Intent(context, MainActivity::class.java)
|
val contentIntent = Intent(context, MainActivity::class.java)
|
||||||
|
@ -183,10 +187,11 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
.setDeleteIntent(pendingStop)
|
.setDeleteIntent(pendingStop)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
private fun getManager(): NotificationManager {
|
private fun getManager(): NotificationManager {
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
reactApplicationContext.getSystemService(NotificationManager::class.java)
|
reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val timersChannel =
|
val timersChannel =
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_ID_PENDING,
|
CHANNEL_ID_PENDING,
|
||||||
|
@ -196,6 +201,8 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
timersChannel.setSound(null, null)
|
timersChannel.setSound(null, null)
|
||||||
timersChannel.description = "Progress on rest timers."
|
timersChannel.description = "Progress on rest timers."
|
||||||
notificationManager.createNotificationChannel(timersChannel)
|
notificationManager.createNotificationChannel(timersChannel)
|
||||||
|
}
|
||||||
|
|
||||||
return notificationManager
|
return notificationManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,4 +212,12 @@ class AlarmModule constructor(context: ReactApplicationContext?) :
|
||||||
const val CHANNEL_ID_PENDING = "Timer"
|
const val CHANNEL_ID_PENDING = "Timer"
|
||||||
const val NOTIFICATION_ID_PENDING = 1
|
const val NOTIFICATION_ID_PENDING = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onHostResume() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHostPause() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean, val duration: Long)
|
class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean, val duration: Long)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
class AlarmService : Service(), OnPreparedListener {
|
class AlarmService : Service(), OnPreparedListener {
|
||||||
private var mediaPlayer: MediaPlayer? = null
|
private var mediaPlayer: MediaPlayer? = null
|
||||||
private var vibrator: Vibrator? = null
|
private var vibrator: Vibrator? = null
|
||||||
|
@ -58,7 +57,7 @@ class AlarmService : Service(), OnPreparedListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playSound(settings: Settings) {
|
private fun playSound(settings: Settings) {
|
||||||
if (settings.noSound) return;
|
if (settings.noSound) return
|
||||||
if (settings.sound == null) {
|
if (settings.sound == null) {
|
||||||
mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon)
|
mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon)
|
||||||
mediaPlayer?.start()
|
mediaPlayer?.start()
|
||||||
|
@ -80,6 +79,9 @@ class AlarmService : Service(), OnPreparedListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doNotify(): Notification {
|
private fun doNotify(): Notification {
|
||||||
|
val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val alarmsChannel = NotificationChannel(
|
val alarmsChannel = NotificationChannel(
|
||||||
CHANNEL_ID_DONE,
|
CHANNEL_ID_DONE,
|
||||||
CHANNEL_ID_DONE,
|
CHANNEL_ID_DONE,
|
||||||
|
@ -88,10 +90,9 @@ class AlarmService : Service(), OnPreparedListener {
|
||||||
alarmsChannel.description = "Alarms for rest timers."
|
alarmsChannel.description = "Alarms for rest timers."
|
||||||
alarmsChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
alarmsChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||||
alarmsChannel.setSound(null, null)
|
alarmsChannel.setSound(null, null)
|
||||||
val manager = applicationContext.getSystemService(
|
|
||||||
NotificationManager::class.java
|
|
||||||
)
|
|
||||||
manager.createNotificationChannel(alarmsChannel)
|
manager.createNotificationChannel(alarmsChannel)
|
||||||
|
}
|
||||||
|
|
||||||
val builder = getBuilder()
|
val builder = getBuilder()
|
||||||
val context = applicationContext
|
val context = applicationContext
|
||||||
val finishIntent = Intent(context, StopAlarm::class.java)
|
val finishIntent = Intent(context, StopAlarm::class.java)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user