0

I've been working on this project for a few days, and either it sends a notification at the moment I schedule it or never. As it stands right now, it creates the channel and the notification, but it never gets sent.

The logic goes this way: First, I start a Service(), which gets data from a JSON (this works perfectly). After some checks and processing, it passes through this process: setScheduleForINI(separateTime(iniTime), remTime) iniTime is the time when the notification should pop up; it's in the format HH:MM. remTime is the interval between notifications; it's in the format MM.


import android.app.AlarmManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale

@Suppress("PrivatePropertyName")
class AlarmService : Service() {
    private val logShow = LogShow()
    private val jsonManage = JsonManage()

    companion object {
        private const val CHANNEL_ID = "SonogiaAlarmChannel"
        private const val CHANNEL_NAME = "Sonogia"
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
        checkJsonGlobalSettings()
    }

    private fun createNotificationChannel() {
        val iniChannel = NotificationChannel(
            CHANNEL_ID,
            CHANNEL_NAME,
            NotificationManager.IMPORTANCE_HIGH
        ).apply {
            description = "Notificaciones de Sonogia"
            enableLights(true)
            enableVibration(true)
            setSound(null, null)
            lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
            setShowBadge(false)
        }
        val notificationManager: NotificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(iniChannel)
        logShow.logShowMsg("AlarmBGService", "Canal de notificaciones creado")
    }

    private fun checkJsonGlobalSettings() {
        logShow.logShowMsg("AlarmBGService", "Revisando configuracion")
        if (!jsonManage.globalCheck(this)) {
            logShow.logShowMsg("AlarmBGService", "Formato diario resibido")
            val date = Date()
            val dayOfWeek = SimpleDateFormat("EEEE", Locale.getDefault()).format(date)
            val alarmTimes = jsonManage.checkJsonAlarms(this, dayOfWeek)
            if (alarmTimes != null) {
                setNotification(alarmTimes[0], alarmTimes[1], alarmTimes[2])
            } else {
                Toast.makeText(this, "No hay alarmas configuradas", Toast.LENGTH_SHORT).show()
            }
        } else {
            logShow.logShowMsg("AlarmBGService", "Formato Global resibido")
            val alarmTimes = jsonManage.checkJsonAlarms(this, "Global")
            if (alarmTimes != null) {
                setNotification(alarmTimes[0], alarmTimes[1], alarmTimes[2])
            } else {
                Toast.makeText(this, "No hay alarmas configuradas", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun setNotification(iniTime: String, remTime: String, wakeTime: String) {
        logShow.logShowMsg("AlarmBGService", "Configuracion leida: iniTime: $iniTime, remTime: $remTime, wakeTime: $wakeTime")
        logShow.logShowMsg("AlarmBGService", "Configurando alarmas: INI")
        setScheduleForINI(separateTime(iniTime), remTime)
        logShow.logShowMsg("AlarmBGService", "Configurando alarmas: WAKE")
        // AlarmReceiver.wakeNotification(wakeTime,this)
    }

    private fun separateTime(time: String): Array<String> {
        val parts = time.split(":")
        logShow.logShowMsg("AlarmBGService", "Hora: ${parts[0]}, Minuto: ${parts[1]}")
        return parts.toTypedArray()
    }

    private fun setScheduleForINI(iniTime: Array<String>, remTime: String) {
        logShow.logShowMsg("AlarmBGService", "IniHour: ${iniTime[0]}, IniMinute: ${iniTime[1]}")
        cancelAlarm() // Cancel existing alarm
        val intent = Intent(applicationContext, NotificationReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            applicationContext, 1, intent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )
        val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val calendar = Calendar.getInstance().apply {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, iniTime[0].toInt())
            set(Calendar.MINUTE, iniTime[1].toInt())
            set(Calendar.SECOND, 0)
        }
        logShow.logShowMsg("AlarmBGService", "Agendando a las: ${calendar.timeInMillis}")
        try {
            if (remTime.isNotEmpty()) {
                if (remTime.toInt() != 0) {
                    alarmManager.setInexactRepeating(
                        AlarmManager.RTC_WAKEUP,
                        calendar.timeInMillis,
                        remTime.toLong() * 1000 * 60,
                        pendingIntent
                    )
                    logShow.logShowMsg("AlarmBGService", "Agendado con intervalo remTime: $remTime")
                } else {
                    alarmManager.set(
                        AlarmManager.RTC_WAKEUP,
                        calendar.timeInMillis,
                        pendingIntent
                    )
                    logShow.logShowMsg("AlarmBGService", "Agendado sin intervalo remTime: $remTime")
                }
            }
        } catch (e: SecurityException) {
            logShow.logShowMsg("AlarmBGService", "Error al programar la alarma: ${e.message}")
        }
        logShow.logShowMsg("AlarmBGService", "Agendado")
    }

    private fun cancelAlarm() {
        val intent = Intent(applicationContext, NotificationReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            applicationContext, 1, intent,
            PendingIntent.FLAG_NO_CREATE // Cancel alarm if it exists
        )
        pendingIntent?.let {
            val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
            alarmManager.cancel(it)
            it.cancel()
            logShow.logShowMsg("AlarmBGService", "Alarm canceled")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        cancelAlarm()
    }
}

this is where all takes place

class NotificationReceiver : BroadcastReceiver() {
    private val logShow = LogShow()

    companion object{
        private const val NOTIFICATION_ID = 1
        private const val CHANNEL_ID = "SonogiaAlarmChannel"
    }

    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    override fun onReceive(context: Context, intent: Intent) {
        iniNotification(context)
        val stop = intent?.getStringExtra("A dormir")
        if (stop == "A dormir"){
            logShow.logShowMsg("NotificationReceiver", "Repeticion detenida")
            Toast.makeText(context, "Buenas noches", Toast.LENGTH_SHORT).show()
        }
    }

    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    private fun iniNotification(context: Context) {
        val iniRemoteViews = RemoteViews(context.packageName, R.layout.notification_layout)
        iniRemoteViews.setOnClickPendingIntent(
            R.id.cancel_button,
            PendingIntent.getBroadcast(
                context, 0,
                Intent(context, NotificationReceiver::class.java).putExtra("action", "cancel_rem"),
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
        )
        val pendingIntent = PendingIntent.getBroadcast(
            context, 0,
            Intent(context, NotificationReceiver::class.java).putExtra("A dormir", "A dormir"),
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        val iniNotification = NotificationCompat.Builder(context, CHANNEL_ID)
            .setContentTitle("Buenas noches")
            .setContentText("Ha llegado la hora de dormir, te deseamos dulces sueños. Att: Sonogia")
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .setCustomContentView(iniRemoteViews)
            .addAction(0, "A dormir", pendingIntent)
        val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.notify(NOTIFICATION_ID, iniNotification)
        logShow.logShowMsg("NotificationReceiver", "IniNotification enviada")
    }
}

and this is where i create the notificication i need to send

i've tryed with diferents methods for seending the notification, with setExact, the place of execution and and other logics, and everything before this works correctly

2
  • did you declare in your app's manifest file notification runtime permission ( <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> ) ? Commented Jun 4, 2024 at 11:34
  • yes, <manifest xmlns:android="schemas.android.com/apk/res/android" xmlns:tools="schemas.android.com/tools"> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <application this are the ones i have declared Commented Jun 4, 2024 at 23:29

1 Answer 1

0

In my opinion you should send the notification directly to the service. In my app, in order to send notifications related to Bluetooth messages, I used the following code directly in the service.

Note "To prevent ad spam and credential phishing, starting with Android 14, the default USE_FULL_SCREEN_INTENT permission to send full-screen notifications can be granted only to apps that provide calling and alarm functionalities." (from https://source.android.com/docs/core/permissions/fsi-limits#:~:text=To%20prevent%20ad%20spam%20and,provide%20calling%20and%20alarm%20functionalities.)

    class BluetoothLeService : Service() {
//omitted
    /**
     * Game1BleActivity -> step 4 -> Game1BleActivity
     * Once the service is bound to, it needs to access the BluetoothAdapter.
     * It should check that the adapter is available on the device.
     * Read Set up Bluetooth for more information on the BluetoothAdapter.
     * The following example wraps this setup code in an initialize() function that returns a Boolean
     * value indicating success.
     *
     * @return Boolean
     */
    fun initialize(context:Context): Boolean {
//omitted
        // Create the NotificationChannel.
        val name = ctext.getString(R.string.notification_channel_name)
        val descriptionText = ctext.getString(R.string.notification_channel_description)
        val importance = NotificationManager.IMPORTANCE_HIGH
        NOTIFICATION_CHANNEL_ID = ctext.getString(R.string.notification_channel_name)
        val mChannel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance)
        mChannel.description = descriptionText
        // Register the channel with the system. You can't change the importance
        // or other notification behaviors after this.
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(mChannel)
        //
        return true
    }
// omitted
    @SuppressLint("MissingPermission")
    fun notifyMessageFromGattServer ()  {
        if (messageFromGattServer != "I'M DISCONNECTING")
        {
            // use comma as separator
            val cvsSplitBy = getString(R.string.character_comma)
            val oneWord: Array<String?> =
                messageFromGattServer.split(cvsSplitBy.toRegex()).toTypedArray()
            //
            if (oneWord[0] == getString(R.string.io))
                { oneWord[0] = " " }
            // Create an explicit intent for an Activity in your app.
            val fullScreenIntent = Intent(this, Game1BleActivity::class.java).apply {
                flags = Intent.FLAG_ACTIVITY_NEW_TASK
            }
            val fullScreenPendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)
            //
            val builder = Notification.Builder(ctext,NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.drawable.heart)
                .setContentTitle(deviceEnabledUserName)
                .setContentText(oneWord[0] + " " + oneWord[4] + " " + oneWord[8])
                // Set the intent that fires when the user taps the notification.
                .setFullScreenIntent(fullScreenPendingIntent, true)
                .setLargeIcon(bitmap1)
                .setStyle(Notification.BigPictureStyle()
                    .bigPicture(bitmap1))
            with(NotificationManagerCompat.from(ctext)) {
                // notificationId is a unique int for each notification that you must define.
                notify(1, builder.build())
            }
            // omitted
        }
    }
//omitted
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.