NTP-66959 | Add android 12 bypass check (#16774)
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
package com.navi.common.pushnotification
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.navi.base.utils.isNotNullAndNotEmpty
|
||||
@@ -29,10 +28,7 @@ object CustomNotificationHandler {
|
||||
} else {
|
||||
when (data[NotificationConstants.TEMPLATE_TYPE]) {
|
||||
TIMER_TEMPLATE -> {
|
||||
if (
|
||||
remoteMessage.priority == RemoteMessage.PRIORITY_HIGH &&
|
||||
Build.VERSION.SDK_INT <= Build.VERSION_CODES.R
|
||||
) {
|
||||
if (remoteMessage.priority == RemoteMessage.PRIORITY_HIGH) {
|
||||
TimerNotificationRenderer.render(context, data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ object TimerNotificationRenderer {
|
||||
notificationManager?.notify(notificationId /* ID of notification */, notification)
|
||||
}
|
||||
|
||||
private fun getChannelId(context: Context): String {
|
||||
fun getChannelId(context: Context): String {
|
||||
return "${context.packageName}.timer.notification"
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.navi.common.pushnotification
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
@@ -16,13 +17,10 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.CountDownTimer
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.navi.base.utils.isNotNull
|
||||
|
||||
enum class TimerState {
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
TERMINATED,
|
||||
}
|
||||
import com.navi.common.R
|
||||
import com.navi.naviwidgets.R as WidgetsR
|
||||
|
||||
class TimerNotificationService : Service() {
|
||||
companion object {
|
||||
@@ -43,34 +41,113 @@ class TimerNotificationService : Service() {
|
||||
override fun onBind(intent: Intent): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent != null) {
|
||||
val notificationId = intent.getIntExtra(NotificationConstants.NOTIFICATION_ID, 0)
|
||||
if (
|
||||
!(intent.action == ACTION_TERMINATE && notificationId != foreGroundNotificationId)
|
||||
) {
|
||||
foreGroundNotificationId =
|
||||
intent.getIntExtra(
|
||||
NotificationConstants.NOTIFICATION_ID,
|
||||
TimerNotificationRenderer.getNotificationId(),
|
||||
)
|
||||
bundle = intent.extras ?: Bundle()
|
||||
bundle.putInt(NotificationConstants.NOTIFICATION_ID, foreGroundNotificationId)
|
||||
when (intent.action) {
|
||||
ACTION_PLAY -> {
|
||||
playTimer(intent.getStringExtra(TIMER_DURATION)?.toLong() ?: -1)
|
||||
}
|
||||
ACTION_STOP -> stopTimer()
|
||||
ACTION_TERMINATE -> terminateTimer()
|
||||
}
|
||||
}
|
||||
// Early return if no intent provided
|
||||
intent ?: return START_NOT_STICKY
|
||||
|
||||
if (!shouldProcessIntent(intent)) {
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
initializeServiceFromIntent(intent)
|
||||
|
||||
processTimerAction(intent)
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
private fun shouldProcessIntent(intent: Intent): Boolean {
|
||||
val notificationId = intent.getIntExtra(NotificationConstants.NOTIFICATION_ID, 0)
|
||||
return !(intent.action == ACTION_TERMINATE && notificationId != foreGroundNotificationId)
|
||||
}
|
||||
|
||||
private fun initializeServiceFromIntent(intent: Intent) {
|
||||
foreGroundNotificationId =
|
||||
intent.getIntExtra(
|
||||
NotificationConstants.NOTIFICATION_ID,
|
||||
TimerNotificationRenderer.getNotificationId(),
|
||||
)
|
||||
|
||||
bundle = intent.extras ?: Bundle()
|
||||
bundle.putInt(NotificationConstants.NOTIFICATION_ID, foreGroundNotificationId)
|
||||
}
|
||||
|
||||
private fun startForegroundServiceImmediately() {
|
||||
// CRITICAL: Call startForeground() IMMEDIATELY after getting notification ID
|
||||
// This prevents ForegroundServiceDidNotStartInTimeException on Android 12+ and
|
||||
// RemoteServiceException
|
||||
val loadingNotification = createLoadingNotification(this)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(
|
||||
foreGroundNotificationId,
|
||||
loadingNotification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
|
||||
)
|
||||
} else {
|
||||
startForeground(foreGroundNotificationId, loadingNotification)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processTimerAction(intent: Intent) {
|
||||
when (intent.action) {
|
||||
ACTION_PLAY -> {
|
||||
// Start foreground service immediately
|
||||
startForegroundServiceImmediately()
|
||||
val duration = intent.getStringExtra(TIMER_DURATION)?.toLong() ?: -1
|
||||
playTimer(duration)
|
||||
}
|
||||
|
||||
ACTION_STOP -> stopTimer()
|
||||
ACTION_TERMINATE -> terminateTimer()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
super.onTaskRemoved(rootIntent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple, lightweight loading notification for immediate startForeground() call. This
|
||||
* prevents ForegroundServiceDidNotStartInTimeException by minimizing processing time.
|
||||
*/
|
||||
private fun createLoadingNotification(context: Context): android.app.Notification {
|
||||
val channelId = TimerNotificationRenderer.getChannelId(this)
|
||||
|
||||
// Ensure notification channel exists for Android 8.0+ (API 26+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel(context, channelId)
|
||||
}
|
||||
|
||||
val smallIcon =
|
||||
bundle.getString(NotificationConstants.SMALL_ICON)?.toInt()
|
||||
?: WidgetsR.drawable.ic_new_navi_logo
|
||||
|
||||
return NotificationCompat.Builder(this, channelId)
|
||||
.setContentTitle("Loading notification...")
|
||||
.setContentText("Preparing")
|
||||
.setSmallIcon(smallIcon)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates notification channel for Android 8.0+ (API 26+) if it doesn't exist. Required for
|
||||
* showing notifications on newer Android versions.
|
||||
*/
|
||||
private fun createNotificationChannel(context: Context, channelId: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
val channel =
|
||||
NotificationChannel(
|
||||
channelId,
|
||||
context.applicationContext.getString(R.string.navi_app_custom_channel_name),
|
||||
NotificationManager.IMPORTANCE_DEFAULT,
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun playTimer(setTime: Long) {
|
||||
if (setTime <= 0) {
|
||||
terminateTimer()
|
||||
@@ -79,18 +156,14 @@ class TimerNotificationService : Service() {
|
||||
this.setTime = setTime
|
||||
secondsRemaining = setTime
|
||||
bundle.putLong(TIME_LEFT, setTime)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(
|
||||
foreGroundNotificationId,
|
||||
TimerNotificationRenderer.createNotification(this, bundle),
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
|
||||
)
|
||||
} else {
|
||||
startForeground(
|
||||
foreGroundNotificationId,
|
||||
TimerNotificationRenderer.createNotification(this, bundle),
|
||||
)
|
||||
}
|
||||
|
||||
// Update the existing foreground notification with full timer content
|
||||
// No need to call startForeground() again - service is already in foreground state
|
||||
val notificationManager =
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val fullTimerNotification = TimerNotificationRenderer.createNotification(this, bundle)
|
||||
notificationManager.notify(foreGroundNotificationId, fullTimerNotification)
|
||||
|
||||
TimerNotificationRenderer.updateTimeLeft(this, bundle)
|
||||
|
||||
timer =
|
||||
@@ -106,7 +179,6 @@ class TimerNotificationService : Service() {
|
||||
}
|
||||
}
|
||||
.start()
|
||||
|
||||
state = TimerState.RUNNING
|
||||
}
|
||||
|
||||
@@ -139,11 +211,16 @@ class TimerNotificationService : Service() {
|
||||
}
|
||||
|
||||
private fun isValidNotification(notificationId: Int): Boolean {
|
||||
val notificationManager =
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
|
||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as? NotificationManager
|
||||
return notificationManager
|
||||
?.activeNotifications
|
||||
?.firstOrNull { it.id == notificationId }
|
||||
.isNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
enum class TimerState {
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
TERMINATED,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user