I'm working on building an alarm clock app. The end goal is to add a specific feature to it, but right now I'm working on making it function as a regular alarm clock.
One problem I'm coming across is that when I run it on my Pixel 4a, the notification to dismiss the alarm comes later than it should. The BroadcastReceiver that launches the Service is called on time, and the ringtone and vibration are activated as soon as the broadcast is received, but I have noticed a delay of up to 15 seconds from when the broadcast is received to the time the alarm notification appears. Another strange thing is that if I set another alarm for a short time later, the notification may appear instantly like it should.
On Android Studio's emulator, the notification appears on time every time EDIT: on API 30. On API 33, the same thing happens as on my phone.
Here is the code for the service:
class AlarmService : Service() {
private lateinit var ringtone: Ringtone
private lateinit var vibrator: Vibrator
override fun onCreate() {
super.onCreate()
vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager =
getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
// Backwards compatibility for API < 31 requires using deprecated service
#Suppress("DEPRECATION")
getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val pendingIntent = getPendingIntent(intent)
val alarmLabel = intent?.getStringExtra(LABEL)
val shouldVibrate = intent?.getBooleanExtra(VIBRATE, false) ?: false
val ringtoneUri = getRingtoneUri(intent)
ringtone = getRingtone(ringtoneUri)
val notification = getNotification(pendingIntent, alarmLabel)
ringAndVibrate(shouldVibrate)
startForeground(1, notification)
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
ringtone.stop()
vibrator.cancel()
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
#SuppressLint("UnspecifiedImmutableFlag") // In android < S, mutability flags not compatible
private fun getPendingIntent(intent: Intent?): PendingIntent {
val notificationIntent = Intent(this, RingActivity::class.java)
moveExtras(intent, notificationIntent)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
}
private fun getRingtoneUri(intent: Intent?): Uri =
intent?.getParcelableExtra(RINGTONE_URI) ?: RingtoneManager.getDefaultUri(
RingtoneManager.TYPE_ALARM
)
private fun getRingtone(ringtoneUri: Uri): Ringtone {
val ringtone = RingtoneManager.getRingtone(this, ringtoneUri)
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
ringtone.audioAttributes = audioAttributes
ringtone.isLooping = true
return ringtone
}
private fun getNotification(pendingIntent: PendingIntent, alarmLabel: String?): Notification =
NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(alarmLabel ?: getString(R.string.notification_default_alarm_label))
.setSmallIcon(R.drawable.ic_baseline_alarm_24)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setFullScreenIntent(pendingIntent, true)
.build()
private fun ringAndVibrate(vibrate: Boolean) {
ringtone.play()
if (vibrate) {
val vibEffect = VibrationEffect.createWaveform(longArrayOf(0, 100, 1000), 0)
vibrator.vibrate(vibEffect)
}
}
}
EDIT: Here is the Android manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="[REDACTED]">
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name=".app.AlarmApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="[REDACTED]">
<activity
android:name=".features.alarmring.RingActivity"
android:exported="false" />
<activity
android:name=".features.mainscreen.MainActivity"
android:exported="true"
android:showOnLockScreen="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".features.alarmscheduling.AlarmBroadcastReceiver"
android:exported="false">
</receiver>
<service android:name=".features.alarmring.AlarmService" />
</application>
</manifest>
Related
I hope you're doing well. Please, if anyone can help me with this, kindly do it. I spent more days and time searching solution for that but I'm not satisfied.
I'm working on a todo and note taking app. About the todo functionality, the user can add a task, set the due date and time and select a ringtone for the todo. The user gets a notification with the ringtone chosen when the date and time set for the todo expire. I used Alarm manager with Broadcast receiver first and everything was working fine when I'm interacting with the app. But when I close the app and the activity is destroyed, the app doesn't send notification anymore. I did more researches on that but the solutions I found didn't work. Amoung the solutions, someone suggested to use service. So I replaced the Broadcast receiver by a service but it doesn't solve my problem. The app sends notification only when I'm interacting with it but when it gets closed, it doesn't send the notification. If there is anything I can do, please kindly help me.
Here is how I set the alarm
The alarm manager sends data to the servive class. All the data are saved in Room database.
object TodoAlarm {
#SuppressLint("SimpleDateFormat", "InlinedApi")
fun setTodoAlarmReminder(context: Context, todo: Todo) {
val alarmManager: AlarmManager? =
context.getSystemService(Context.ALARM_SERVICE) as AlarmManager?
val timeFormat = SimpleDateFormat("hh:mm a")
val parsedTime = timeFormat.parse(todo.time)
val expectedFormat = SimpleDateFormat("hh:mm")
val newTime = expectedFormat.format(parsedTime!!)
Log.d(TODO_ALARM_TAG, "NEW FORMATTED TIME IS $newTime")
val (todoHour, todoMinute) = newTime.split(":").map { it }
Log.d(TODO_ALARM_TAG, "THE HOUR IS $todoHour and minute is $todoMinute")
val intent = Intent(context, TodoAlarmService::class.java)
val pendingIntent = PendingIntent.getService(
context,
todo.id,
intent.apply {
action = SET_ACTION
putExtra("name", todo.name)
putExtra("date", todo.date)
putExtra("time", todo.time)
putExtra("id", todo.id)
putExtra("todo", todo)
putExtra("ringtoneUri", todo.ringtoneUri)
putStringArrayListExtra("days", todo.repeatFrequency as ArrayList<String>?)
Log.d(TODO_ALARM_TAG, "${todo.id}")
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT
)
val dateAndTimeReminder = "${todo.date} ${todo.time}"
Log.d(TODO_ALARM_TAG, "Date and Time $dateAndTimeReminder")
val format = SimpleDateFormat("dd/MM/yyyy h:mm a")
val todoTime = format.parse(dateAndTimeReminder)
val currentTime = Calendar.getInstance().time
Log.d(TODO_ALARM_TAG, "TODO TIME $todoTime")
Log.d(TODO_ALARM_TAG, "CURRENT TIME $currentTime")
if (todoTime >= currentTime) {
if (todo.repeatFrequency != null && todo.repeatFrequency.isNotEmpty()){
alarmManager?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
todoTime.time,
AlarmManager.INTERVAL_DAY,
pendingIntent
)
} else {
alarmManager.setInexactRepeating(
AlarmManager.RTC_WAKEUP,
todoTime.time,
AlarmManager.INTERVAL_DAY,
pendingIntent
)
}
}
}else{
alarmManager?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
todoTime.time,
pendingIntent
)
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
todoTime.time,
pendingIntent
)
}
}
}
}
}
}
The service class the data. I didn't past all the code here because it's too long but it how it looks like.
class TodoAlarmService : Service(){
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null) {
showNotification(intent)
}
return START_STICKY
}
override fun onBind(p0: Intent?): IBinder? {
TODO("Not yet implemented")
}
fun showNotification(intent: Intent){
val context = applicationContext
val name = intent.getStringExtra("name")
val date = intent.getStringExtra("date")
val time = intent.getStringExtra("time")
val uri = intent.getStringExtra("ringtoneUri")
val dayList = intent.getStringArrayListExtra("days")
val todoDateAndTime = "$date $time"
val id = intent.getIntExtra("id", 0)
val notificationHelper = NotificationHelper(context)
val sound = RingtoneManager.getRingtone(context, uri?.toUri())
val startServiceIntent = Intent(context, TodoRingtoneService::class.java)
startServiceIntent.putExtra("ringtoneUri", uri)
val calendar = Calendar.getInstance()
val day = calendar.get(Calendar.DAY_OF_WEEK)
if (SET_ACTION == intent.action) {
if (id >= 0) {
if (dayList != null) {
when (dayList.size) {
0 -> {
context.startService(startServiceIntent)
notificationHelper.onCreateNotification(name = name!!, todoDateAndTime)
}
1 -> {
if (dayList[0] == "Daily") {
if (day == Calendar.MONDAY || day == Calendar.TUESDAY ||
day == Calendar.WEDNESDAY || day == Calendar.THURSDAY || day == Calendar.FRIDAY ||
day == Calendar.SATURDAY || day == Calendar.SUNDAY
) {
context.startService(startServiceIntent)
notificationHelper.onCreateNotification(name = name!!,
todoDateAndTime)
}
} else {
if (dayList[0] == "Monday") {
if (day == Calendar.MONDAY) {
context.startService(startServiceIntent)
notificationHelper.onCreateNotification(name = name!!,
todoDateAndTime)
}
}
...
I start the service in onStop main activity method.
#SuppressLint("NewApi")
override fun onStop() {
super.onStop()
startTodoAlarmService()
}
private fun startTodoAlarmService(){
val serviceIntent = Intent(this, TodoAlarmService::class.java)
applicationContext.startService(serviceIntent)
}
My Manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="eg.esperantgada.dailytodo">
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<application
android:name=".application.TodoApplication"
android:allowBackup="true"
android:fullBackupContent="true"
android:hardwareAccelerated="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.DailyToDo"
tools:ignore="DataExtractionRules,ExtraText">
<activity
android:name=".SecondStartActivity"
android:exported="false"
android:launchMode="singleTask"
android:theme="#style/Theme.AppCompat.NoActionBar"/>
<activity
android:name=".StartActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:launchMode="singleTop"
android:theme="#style/Theme.AppCompat.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".SettingsActivity"
android:label="#string/title_activity_settings"
android:parentActivityName=".MainActivity" />
<receiver android:name=".broadcastreceiver.TodoAlarmReceiver"
android:enabled="true"/>
<receiver android:name=".broadcastreceiver.StopRingtoneReceiver"
android:enabled="true"/>
<receiver android:name=".broadcastreceiver.CancelNotificationReceiver"/>
tools:ignore="Instantiatable" />
<service android:name=".service.TodoRingtoneService"/>
<service android:name=".service.TodoAlarmService"
android:exported="true"
android:label="#string/app_name"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="eg.esperantgada.TodoAlarmService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-1581485430995496~1199027256" />
</application>
</manifest>
If you want me to give any other details, kindly let me know. I'll really appreciate your help. Thanks
I create app in react native, that needs to run foreground service containing native code. The service itself works well but after killing main application process (by pressing square button and swiping the app away) - it sometimes returns without any complications on launch from icon or notification but it oftens displays blank screen (without any activity launched) and prints handleWindowVisibility: no activity for token android.os.BinderProxy in logcat. I've been googling it for a few hours but there is little feedback on that matter and nothing seems to work for me.
The class I use to communicate through RCT Bridge
#ReactMethod
fun init() {
val service = Intent(context, MyService::class.java)
service.action = SERVICE_ACTION_EXECUTE
if(!isMyServiceRunning(MyService::class.java)) {
Log.i("MyService", "Starting new service")
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(service)
} else {
context.startService(service)
}
}
val serviceConnection = object: ServiceConnection {
override fun onServiceConnected(p0: ComponentName?, binder: IBinder?) {
myService = (binder as MyService.LocalBinder).getService()
myService?.bindClient(this#Integrator)
}
override fun onServiceDisconnected(p0: ComponentName?) {
myService?.unbindClient()
}
}
context.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE)
}
private fun isMyServiceRunning(serviceClass: Class<*>): Boolean {
val manager: ActivityManager? =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager?
for (service in manager?.getRunningServices(Int.MAX_VALUE)!!) {
if (serviceClass.name == service.service.className) {
return true
}
}
return false
}
My foreground service:
#RequiresApi(api = Build.VERSION_CODES.O)
private fun createNotificationChannel() {
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val name: CharSequence =
getString(R.string.app_name)
val channel =
NotificationChannel(NOTIFICATION_CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW)
notificationManager.createNotificationChannel(channel)
}
private fun buildNotification(): Notification {
val intent = Intent(applicationContext, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, 0)
if(notificationBuilder == null) {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
notificationBuilder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SERVICE)
.setOngoing(true)
}
// Set Exit button action
val exitIntent =
Intent(applicationContext, MyService::class.java).setAction(SERVICE_ACTION_STOP)
notificationBuilder!!.addAction(
android.R.drawable.ic_delete,
getString(R.string.close),
PendingIntent.getService(applicationContext, 0, exitIntent, 0)
)
return notificationBuilder!!.build()
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
createNotificationChannel()
val notification = buildNotification()
startForeground(NOTIFICATION_FOREGROUND_SERVICE_ID, notification)
val action = intent?.action
if(action != null){
when(action) {
SERVICE_ACTION_EXECUTE -> {
Log.d(TAG, "Service executed")
executor.execute(IncomingIntentRouter(intent))
}
SERVICE_ACTION_STOP -> {
Log.d(TAG, "Service stopped")
stopService()
}
}
} else {
Log.d(TAG, "Got null onStartCommand() action")
}
return START_STICKY
}
Android manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.app">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:name=".MainApplication"
android:label="#string/app_name"
android:icon="#mipmap/ic_launcher"
android:roundIcon="#mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="#style/AppTheme"
android:networkSecurityConfig="#xml/network_security_config"
android:extractNativeLibs="true">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyService" />
</application>
I try to create daily notification for my application. In order to test the application I set the interval for the notifications to 15 minutes. Unfortunately, no notification is shown. Neither when the application is running nor when it is closed. I tried to inspire myself with these solutions:
https://stackoverflow.com/questions/33055129/how-to-show-a-notification-everyday-at-a-certain-time-even-when-the-app-is-close
https://developer.android.com/codelabs/android-training-alarm-manager#0
https://github.com/google-developer-training/android-fundamentals-apps-v2/tree/master/StandUp (repository for the previous link)
I added uses-permissions and receiver to the manifest file.
manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ovu">
<uses-permission android:name = "android.permission.VIBRATE" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
<!-- Permission to start Alarm on device reboot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
...
</activity>
<receiver android:name= ".DailyNotificationReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
This class extends BroadcastReceiver class and overrides onReceive method.
DailyNotificationReceiver
class DailyNotificationReceiver : BroadcastReceiver() {
companion object {
// Notification ID.
private const val NOTIFICATION_ID = 0
// Notification channel ID.
private const val PRIMARY_CHANNEL_ID = "primary_notification_channel"
}
private lateinit var mNotificationManager: NotificationManager
private val notificationContent = "Please measure your temperature and cervical mucus"
private val contentTitle = "Provide me your temperature and cervical mucus!"
override fun onReceive(context: Context, intent: Intent) {
mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Deliver the notification.
deliverNotification(context);
}
private fun deliverNotification(context: Context) {
val contentIntent = Intent(context, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(context, NOTIFICATION_ID, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)
// Build the notification
val builder = NotificationCompat.Builder(context, PRIMARY_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_dialog_alert)
.setContentTitle(contentTitle)
.setContentText(notificationContent)
.setContentIntent(contentPendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
// Deliver the notification
mNotificationManager.notify(NOTIFICATION_ID, builder.build())
}
}
In this activity, instance of AlarmManager should set daily repeating.
ActivityNotification
class ActivityNotification : AppCompatActivity() {
companion object {
// Notification ID.
private const val NOTIFICATION_ID = 0
// Notification channel ID.
private const val PRIMARY_CHANNEL_ID = "primary_notification_channel"
}
private lateinit var mNotificationManager: NotificationManager
private fun notifyUser(){
val notifyIntent = Intent(this, DailyNotificationReceiver::class.java)
val notifyPendingIntent = PendingIntent.getBroadcast(this, NOTIFICATION_ID, notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val repeatInterval = AlarmManager.INTERVAL_FIFTEEN_MINUTES
val triggerTime = (SystemClock.elapsedRealtime()
+ repeatInterval)
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerTime, repeatInterval,
notifyPendingIntent)
// Create the notification channel.
createNotificationChannel();
Toast.makeText(this, "Alarm's set", Toast.LENGTH_LONG).show()
}
private fun createNotificationChannel() {
// Create a notification manager object.
mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
// Notification channels are only available in OREO and higher.
// So, add a check on SDK version.
if (Build.VERSION.SDK_INT >=
Build.VERSION_CODES.O) {
// Create the NotificationChannel with all the parameters.
val notificationChannel = NotificationChannel(PRIMARY_CHANNEL_ID,
"Stand up notification",
NotificationManager.IMPORTANCE_HIGH)
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.RED
notificationChannel.enableVibration(true)
notificationChannel.description = "Notifies every 15 minutes to " +
"stand up and walk"
mNotificationManager.createNotificationChannel(notificationChannel)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notification)
editTextNotificationHour = findViewById(R.id.editTextChangedNotification)
mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
}
fun changeNotificationHour(view: View){
notifyUser()
val intent = Intent(this, ProfileActivity::class.java)
startActivity(intent)
finish()
}
I'd be really happy if you guys can help me to find the solution.
Firstly, let's start by enabling our BroadcastReceiver in Manifest by changing our code to this:
<receiver
android:name= ".DailyNotificationReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
Secondly, I am a bit unsure of the purpose of the ActivityNotification Activity.
It would be more than enough to create a simple class like Alarms.
From there you can define all the methods you need to set up an alarm. Something like this:
class InternetDaysLeftAlarm #Inject constructor(
#ApplicationContext val context: Context
) {
/**
* Function is used to schedule one-time Alarm that will trigger on specific time.
*
* #param hourOfDay -> parameter defines what o'clock should the alarm be triggered at. (24-hour)
* default value is:
* #see INTERNET_DAYS_LEFT_ALARM_DEFAULT_TRIGGER_HOUR
*/
fun scheduleAlarm(hourOfDay: Int = INTERNET_DAYS_LEFT_ALARM_DEFAULT_TRIGGER_HOUR) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, InternetDaysLeftReceiver::class.java)
intent.action = INTENT_ACTION_INTERNET_DAYS_LEFT_ALARM
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
val msUntilTriggerHour: Long = TimeUnit.MINUTES.toMillis(minutesUntilOClock(hourOfDay))
// Calculating and adding jitter in order to ease load on server
val jitter: Long = TimeUnit.MINUTES.toMillis(Random.nextInt(0, 420).toLong())
val alarmTimeAtUTC: Long = System.currentTimeMillis() + msUntilTriggerHour + jitter
// Enabling BootReceiver
val bootReceiver = ComponentName(context, BootReceiver::class.java)
context.packageManager.setComponentEnabledSetting(
bootReceiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
/**
* Schedules the Alarm based on Android Version.
*
* As per AlarmManager documentation to guarantee Alarm execution at specified time we use following methods:
*
* #see AlarmManager.setExactAndAllowWhileIdle -> Android.M [API 23] and above.
* #see AlarmManager.setAlarmClock -> Everything below Android M.
*/
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, pendingIntent)
} else {
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(alarmTimeAtUTC, pendingIntent), pendingIntent)
}
}
Notifications can be handle by:
Creating a separate Notifications class
or
Handling all the notification things like creating channel and sending them inside BroadcastReceiver when you receive an Alarm.
I want to notify at midnight. So I used alrammanager. the alrammanager is working well when it is in foreground or background. But the problem is occured when my app is killed. The alrammanager sometimes works or not. I don't understand this weird happening...
For example, when the notification occurs every three minutes, the app works well even if it is in the foreground or background. But when the app kiiled, the notification is occured or not even though there is no change in code. Please help me...
MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val picker = findViewById(R.id.timePicker) as TimePicker
picker.setIs24HourView(true)
val nextNotifyTime: Calendar = GregorianCalendar()
val nextDate: Date = nextNotifyTime.getTime()
val currentTime: Date = nextNotifyTime.getTime()
val HourFormat = SimpleDateFormat("kk", Locale.getDefault())
val MinuteFormat = SimpleDateFormat("mm", Locale.getDefault())
val pre_hour: Int = HourFormat.format(currentTime).toInt()
val pre_minute: Int = MinuteFormat.format(currentTime).toInt()
if (Build.VERSION.SDK_INT >= 23) {
picker.hour = pre_hour
picker.minute = pre_minute
} else {
picker.currentHour = pre_hour
picker.currentMinute = pre_minute
}
val button: Button = findViewById(R.id.button) as Button
button.setOnClickListener {
val hour: Int
val hour_24: Int
val minute: Int
if (Build.VERSION.SDK_INT >= 23) {
hour_24 = picker.hour
minute = picker.minute
} else {
hour_24 = picker.currentHour
minute = picker.currentMinute
}
if (hour_24 > 12) {
hour = hour_24
}
else {
hour = hour_24- 12
}
val calendar: Calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
if(calendar.after(Calendar.getInstance())) {
//calendar.add(Calendar.DATE, 1)
}
diaryNotification(calendar)
}
}
fun diaryNotification(calendar: Calendar) {
val pm = this.packageManager
val receiver = ComponentName(this, DeviceBootReceiver::class.java)
val alarmIntent = Intent(this, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, 60*1000, pendingIntent)
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP)
}
AlarmReceiver
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationIntent = Intent(context, MainActivity::class.java)
notificationIntent.flags = (Intent.FLAG_ACTIVITY_CLEAR_TOP
or Intent.FLAG_ACTIVITY_SINGLE_TOP)
var notification = CustomNotification.getNotificationBuilderInstance(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelName = "channel name"
val importance = NotificationManager.IMPORTANCE_LOW
val channel = NotificationChannel(CHANNEL_ID, channelName, importance)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(NOTIFICATION_ID, notification.build())
}
}
DeviceBootReceiver
class DeviceBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Objects.equals(intent.action, "android.intent.action.BOOT_COMPLETED")) {
val alarmIntent = Intent(context, AlarmReceiver::class.java)
val pendingIntent =
PendingIntent.getBroadcast(context, 0, alarmIntent, 0)
val manager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val millis = Calendar.getInstance().timeInMillis
manager.setRepeating(
AlarmManager.RTC_WAKEUP, millis,
60*1000, pendingIntent
)
}
}
}
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.haii.alarmdemo">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:name=".GlobalApplication"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".DeviceBootReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".AlarmReceiver" />
</application>
</manifest>
go to settings -> app -> YOUR APK NAME -> enable AUTOSTART (or something similar)
now even if your app is killed you will receive your notification
Also I suggest you to take a look at this link: LINK
Hope this "solution" can help you, cya ;)
In my application, I need a foreground service that will check a certain condition every minute, and if it is correct, it triggers a notification reminder. The user determines in advance what time and day he would like to have the reminder to. Data is saved in the database. Then the service every minute checks if it has a reminder for a given hour and day and if so sends a notification. The service must work when the user uses the application, when the application runs in the background and when it is closed. Could someone tell me why this code works on one phone but not on others? So-called, if I set a reminder, up to 20 minutes, it works (in all 3 states that I wrote about earlier), but once I set the reminder for the next days, it doesn't work anymore. I am surprised that sometimes the reminder on another phone works and sometimes it doesn't. I checked, the permission for the application to run in the background is selected in the settings. Please help.
Manifest
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="false"
android:icon="#drawable/pills"
android:label="#string/nameOfApplications"
android:roundIcon="#drawable/icon"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<service
android:name=".ForegroundService"
android:enabled="true"
android:exported="true"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
ForegroundService
class ForegroundService : Service() {
companion object {
val CHANNEL_ID = "ForegroundServiceChannel"
val CHANNEL_ID_CHILD = "ForegroundServiceChannelCHILD"
private var isRunning = false
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val input = intent.getIntExtra("time",15)
createNotificationChannel()
val notificationIntent = Intent(this, Menu::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("MotherNotification")
.setContentText("Message")
.setOnlyAlertOnce(true)
.build()
startForeground(1, notification)
isRunning = true
val context = this
val intent = Intent(this, ShowAll::class.java)
val pendingIntentNotification = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
doAsync {
while(isRunning)
{
var message : String = createReminderMessage(context)
//SystemClock.sleep(input * 10_000L)
SystemClock.sleep(50000)
uiThread {
if(isRunning && (message != "Nadszedł czas by zażyć: ")) {
val notification = NotificationCompat.Builder(context, CHANNEL_ID_CHILD)
.setContentTitle("Title")
.setContentText(message)
.setContentIntent(pendingIntentNotification)
.setAutoCancel(true)
.build()
with(NotificationManagerCompat.from(context)) {
notificationManager.notify(2, notification)
}
}
}
}
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
isRunning = false
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val serviceChannel2 = NotificationChannel(
CHANNEL_ID_CHILD,
"Foreground Service ChannelChild ",
NotificationManager.IMPORTANCE_DEFAULT
//NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
manager.createNotificationChannel(serviceChannel2)
}
}
fun reminderForNow(context: Context) : ArrayList<Reminder> {
var listOfReminder : ArrayList<Reminder> = ArrayList()
var timetoday = takeTimeNow()
var dateToday = takeTodayDate()
val dbHelper = SQLConector(context)
val allRemindersList = dbHelper.getAllReminders()
for (i: Reminder in allRemindersList) {
if (i.reminderDate == dateToday && i.ReminderTime == timetoday) {
var reminder = Reminder(
i.id,
i.Name,
i.reminderDate,
i.ReminderTime
)
listOfReminder.add(reminder)
}
}
return listOfReminder
}
private fun createReminderMessage(p0: Context) : String{
var message : String = "title : "
var listOfReminders = reminderForNow(p0)
if(listOfReminders.count() > 0){
for (i: Reminder in listOfReminders) {
message += i.Name + ", "
}
}
return message
}
private fun takeTodayDate():String{
val current = LocalDateTime.now()
val formatDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
var dateResult = current.format(formatDate).toString()
return dateResult
}
private fun takeTimeNow() : String{
val current = LocalDateTime.now()
val formatTime = DateTimeFormatter.ofPattern("HH:mm")
var timeResult = current.format(formatTime).toString()
return timeResult
}
}
Main Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.buttonStart.setOnClickListener { startService() }
binding.buttonStop.setOnClickListener {stopService() }
startService()
}
private fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.putExtra("time", 1)
ContextCompat.startForegroundService(this, serviceIntent)
}
private fun stopService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
stopService(serviceIntent)
}
}
The correct way to handle tasks which require exact fire time is to use the AlarmManagerCompat class.
You can use setExactAndAllowWhileIdle(...) method to force the alarm to start your service even when the device is in Doze mode and you will need a BroadcastReceiver to re-schedule the alarms if the device is rebooted.
You can find some references online on how to implement that.