NTP-66959 | Add android 12 bypass check (#16774)

This commit is contained in:
Kishan Kumar
2025-06-30 19:43:20 +05:30
committed by GitHub
parent 6eda206419
commit 0f266a1bec
3 changed files with 120 additions and 47 deletions

View File

@@ -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)
}
}

View File

@@ -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"
}

View File

@@ -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,
}