Start activity from receiver in Android Q - android

I'm checking my app with the Android Q [beta 6] in order to add all the required changes to be fully-compatible with the last SO. However, I found out that I am using a Receiver to start an Activity from background and due to the last background limitations implemented (https://developer.android.com/preview/privacy/background-activity-starts) the activity is not being opened.
I tried to use both the receiver context and application context to start the activity but in both cases the system shows a toast saying that is not possible to start activity from background.
What I tried on the Receiver...
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context?.applicationContext?.let {
it.startActivity(Intent(it, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
PushUtils.showReceiverCalledNotification(it)
}
}
That way I wanted to start MyActivity and also show a notification when the receiver is called. Instead, I can see the notification but the Activity is never started. It is very important for the feature to start the activity immediately, so there is a way to continue starting the activity from the receiver?

It is very important for the feature to start the activity immediately, so there is a way to continue starting the activity from the receiver?
No, sorry. Use a high-priority notification, so it appears in "heads-up" mode. The user can then rapidly tap on it to bring up your activity.

Due to restrictions, you cannot start activity from background. Instead you can use notifications as CommonsWare suggested and also suggested on the android developer site.
Here's the official documentation that lists situations when it will work and when won't.
https://developer.android.com/guide/components/activities/background-starts
You can use something like this:
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
showNotification(context.applicationContext)
} else {
context.applicationContext.startActivity(Intent(context, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
}
PushUtils.showReceiverCalledNotification(context)
}
private fun showNotification(context: Context) {
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("default", "default", NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val intent = Intent(context, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
with(NotificationCompat.Builder(context, "default")) {
setSmallIcon(R.drawable.ic_scan_colored)
setContentTitle("Custom Title")
setContentText("Tap to start the application")
setContentIntent(pendingIntent)
setAutoCancel(true)
manager.notify(87, build())
}
}
}

Related

Check for network status update while application killed

My scenario is the following:
I'm working on a chat application and I would like to implement some type synchronization service that starts itself when device recovers network connection. Anytime device has network connection again, unsent messages are going to be automatically sent. With independence of application state (foregorund, background or killed).
Options tried:
1. Broadcast Receiver with android.net.conn.CONNECTIVITY_CHANGE
This scenario only works when the application is active (Foreground or Backround) but stops working when app is killed.
2. Foreground service
A notification is going to be shown all the time which is not ideal. Also I want to avoid draining users' battery.
3. AndroidX.Work.Worker
PeriodicWorkRequest networkCheckingPeriodicWork = PeriodicWorkRequest.Builder.
From<ConnectivityChangeWroker>(repeatInterval:30, repeatIntervalTimeUnit: Java.Util.Concurrent.TimeUnit.Minutes, flexInterval:25, flexIntervalTimeUnit:Java.Util.Concurrent.TimeUnit.Minutes)
.SetConstraints(new Constraints.Builder().SetRequiredNetworkType(AndroidX.Work.NetworkType.Connected)
.SetRequiredNetworkType(AndroidX.Work.NetworkType.Unmetered).Build()).Build();
WorkManager.Instance.EnqueueUniquePeriodicWork("", ExistingPeriodicWorkPolicy.Replace, networkCheckingPeriodicWork);
public class ConnectivityChangeWroker : AndroidX.Work.Worker
{
public ConnectivityChangeWroker(Context context, WorkerParameters workerParameters) : base(context, workerParameters)
{
}
public override Result DoWork()
{
try
{
//Start synch service
return Result.InvokeSuccess();
}
catch (Exception)
{
return Result.InvokeFailure();
}
}
}
But in this case, I'm not achieving the desired behaviour. For my undestanding, I just set a periodic work that checks for network connection, and if there is one, runs DoWork() method.
-- EDIT --
4.JobService
Java.Lang.Class javaClass = Java.Lang.Class.FromType(typeof(ConnectivityChangeJob));
ComponentName component = new ComponentName(Application.Context, javaClass);
JobInfo jobInfo = new JobInfo.Builder(1, component)
.SetRequiredNetworkType(Android.App.Job.NetworkType.Any)
.SetOverrideDeadline(5000)
.SetPersisted(true)
.Build();
JobScheduler jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
jobScheduler.Schedule(jobInfo);
[Service(Name = "Extintores.ConnectivityChangeJob", Permission = "android.permission.BIND_JOB_SERVICE")]
public class ConnectivityChangeJob : JobService
{
private Intent startServiceIntent;
public ConnectivityChangeJob()
{
}
public override bool OnStartJob(JobParameters jobParams)
{
//Start synchService
return true;
}
public override bool OnStopJob(JobParameters jobParams)
{
return true; //Reschedule the job
}
}
But in this case, OnStartJob is only fired the first time the applicatio is opened and, apparently, never again.
Is there any way I can achieve what I'm aming for?
-- EDIT --
I want to achieve the same bahaviour as applications as WhatsApp. When it detects network connection again, automatically all unsent messages are going to be send.
I guess the AndroidX.Work.Worker is the best option.
In DoWork you should update databases and send requests.
Besides worker supports long-running workers
Example DownloadWorker:
class DownloadWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
override suspend fun doWork(): Result {
val inputUrl = inputData.getString(KEY_INPUT_URL)
?: return Result.failure()
val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
?: return Result.failure()
// Mark the Worker as important
val progress = "Starting Download"
setForeground(createForegroundInfo(progress))
download(inputUrl, outputFile)
return Result.success()
}
private fun download(inputUrl: String, outputFile: String) {
// Downloads a file and updates bytes read
// Calls setForeground() periodically when it needs to update
// the ongoing Notification
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = applicationContext.getString(R.string.notification_channel_id)
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.cancel_download)
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
// Create a Notification channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(notificationId, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
// Create a Notification channel
}
companion object {
const val KEY_INPUT_URL = "KEY_INPUT_URL"
const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
}
}

Android timer when phone is idle

I am trying to create an Android app which plays a sound every few seconds. I want this to work even when the phone is idle. At the moment everything works fine even when the phone screen is off. But after about a minute, the timer stops working. As soon as the screen is turned back on, the missed sounds are played in quick succession. I struggle to find the right terms and concepts to properly find a solution with Google.
When I first encountered this issue, I made sure that my service was running in the background. As it seems, the service is also enabled in the background because everything works fine as long as the screen is not turned off.
Code for running the service in the background:
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
timer.scheduleAtFixedRate(TimeTask(), 0, 100);
return START_STICKY
}
private inner class TimeTask() : TimerTask() {
override fun run() {
sendBroadcast(Intent(TIMER_UPDATED))
}
}
Since this didn't work, I tried to make the service a foreground service. But this didn't work either. (I tried to do it as shown here)
Code for running the service in foreground:
private fun runInBackground() {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("service", "something")
} else {
""
}
val notification: Notification = NotificationCompat.Builder(this, channelId)
.setContentTitle("title")
.setContentText("text")
.setSmallIcon(R.drawable.alert_dark_frame)
.build()
startForeground(1, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String{
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
.createNotificationChannel(channel)
return channelId
}
I read something about scheduling tasks in Android. And found the AlarmManager, but I don't think this would really work the way I want it to because I would have to schedule an alarm for every 100ms. The official doc also states that this shouldn't be used in that way and that "handlers" are more suited, but I struggle to understand how I could replace my current timer with such a handler. I have tried to implement something, but failed.
val updateHandler = Handler()
val runnable = Runnable {
// some action
}
updateHandler.looper(runnable, 100)
Finally solved my problem. Something that I didn't understand before was a "wakelock". It looks something like this:
val wakeLock: PowerManager.WakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ShuttleRun::DefaultWakeLock").apply {
acquire(30*60*1000L)
}
}
It basically just keeps the device awake, which doesn't mean that the screen is turned on, but rather that the service can run in the background without being disturbed.

Android Dynamic BroadcastReceiver not receiving broadcast

I am trying to set an alarm for a 15 minute reminder (Testing with 5 seconds first). When I register the receiver in my Manifest, it works perfect. When I try to set it as a dynamic broadcast receiver, I can't get it to work. The onReceive() method doesn't get called at all. I can see this by putting in Log.d() entries
The reason I need to convert it to a dynamic broadcast receiver is because I can't figure out how to access my MainActivity from inside the onReceive() method, as I need to perform some UI tasks like show a notification etc.
I am setting up the AlarmManager like this:
fun snoozeTask(context: Context, taskId: String, snoozeTime: Long) {
alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
intent.putExtra("taskId", taskId)
intent.action = AlarmReceiver.SNOOZE_TASK
PendingIntent.getActivity(context, ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
(context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager)?.let { alarmManager ->
val hasPermissions = alarmManager.canScheduleExactAlarms()
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.ELAPSED_REALTIME_WAKEUP, snoozeTime, alarmIntent)
}
}
and then my onReceive method:
class AlarmReceiver : BroadcastReceiver() {
companion object {
const val SNOOZE_TASK = "com.my.app.SNOOZE_TASK"
}
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED -> {
// reschedule all the exact alarms
//TODO: When the user revokes permission for
// <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
}
SNOOZE_TASK -> {
val taskId = intent.extras?.getString("taskId")
val snoozeTime = intent.extras?.getLong("snoozeTime")
}
else -> Log.d("TAG", "AlarmReceiver::onReceive::ELSE")
}
}
}
Then in my MainActivity I have this:
private var alarmReceiver: AlarmReceiver = AlarmReceiver()
and then in MainActivity onCreate:
registerReceiver(alarmReceiver, IntentFilter(AlarmReceiver.SNOOZE_TASK))
With the same code, this works when I register it in the Manifest. When I try to register in MainActivity, it doesn't work.
Eventually I want to pass some MutableLiveData to the constructor of my AlarmReceiver class so when I receive the taskId, it gets passed back to MainActivity this way.
What am I doing wrong?
Thanks

Android Notification Timeout Listener

I did implement a notification feature in android using the Notification.Builder in Android OREO+. I need to cancel the notification after a certain time frame, if the user has not clicked on the notification. which i completed using the setTimeOutAfter method.
https://developer.android.com/reference/android/app/Notification.Builder.html#setTimeoutAfter(long).
Now, i need to send a message to server that the notification wasn't clicked/timeout has occured. How can i implement this? Is there any notificationTimeout Listener?
There's nothing like a timeout listener but you can use a delete intent for your purpose. You'll need a Broadcast Receiver in order to do something (like calling your server) when the notification gets dismissed.
In code:
class NotificationDismissedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// call your server here
}
}
private fun getNotificationWithDeleteIntent() : Notification{
val deleteIntent = Intent(context, NotificationDismissedReceiver::class.java)
deleteIntent.action = "notification_cancelled"
val onDismissPendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle(textTitle)
.setContentText(textContent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setTimeoutAfter(TIMEOUT)
.setDeleteIntent(onDismissPendingIntent)
return builder.build()
}

Android service getting killed [duplicate]

This question already has answers here:
Background service for android oreo
(3 answers)
Closed 2 years ago.
I am trying to make an android app. It should do something when the phone gets connected to a particular wifi network, and do nothing the rest of the time. I use a Service and a BroadcastReceiver. Everything works fine, but the service for checking wifi state is killed for no reason in some seconds after I hide my application. Is it possible to make it persistent?
I know about android:persistent flag, but it seems to be useless for me as my app isn't system.
As of Android Oreo no background services are allowed to run when the app is closed so you must foreground start it (Google recommends using JobScheduler for SDK > Oreo).This is not the perfect solution of course but should get you started.
class NotificationService: Service() {
private var notificationUtils = NotificationUtils(this)
private var notificationManager: NotificationManager? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_STICKY
}
override fun onCreate() {
super.onCreate()
//here checking if sdk > Oreo then start foreground or it will start by default on background
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(System.currentTimeMillis().toInt(), notificationUtils.foregroundNotification())
}
}
override fun onDestroy() {
super.onDestroy()
// when the OS kills the service send a broadcast to restart it
val broadcastIntent = Intent(this, NotificationServiceRestarterBroadcastReceiver::class.java)
sendBroadcast(broadcastIntent)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
class NotificationServiceRestarterBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(Intent(context, NotificationService::class.java))
}else {
// if sdk < Oreo restart the background service normally
context.startService(Intent(context, NotificationService::class.java))
}
}
}

Categories

Resources