I wrote a foreground service to make sure my app can continue running when put into the background. The app needs to run in the background because after its timer elapses, it sounds a tone and vibrates to alert the user. However, when the Power or Home button is pressed, the app's timer stops running after about 15 minutes unless the phone is plugged into power. The phone was fully charged when I tested this.
Incidentally, I also set the app to NOT be optimized for battery life after reading on various sites that that would ensure that the app would continue running. From everything I read, I'm doing everything right, yet I still can't get this to work. I'm running Android 11 on a Pixel 2. I know that Google limited foreground processing for later versions of Android, but setting the app to not optimize for battery life is supposed to get around the problem, isn't it? To be safe, when the app starts, it asks the user to approve background operation:
PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(APPLICATION_ID)) {
// Ask user to allow app to not optimize battery life. This will keep
// the app running when the user puts it in the background by pressing
// the Power or Home button.
Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + APPLICATION_ID));
startActivity(intent);
}
so the user sees the following when the app is run and while optimized for battery:
I start the foreground service as follows:
private void startForegroundMonitoring() {
broadcastIntent = new Intent(context, BroadcastService.class);
broadcastIntent.putExtra(ALLOWEDTIME, allowed_time);
broadcastIntent.putExtra(BEEP, beep.isChecked());
broadcastIntent.putExtra(VIBRATE, vibrate.isChecked());
broadcastIntent.putExtra(NOTIFY, notify_monitor.isChecked());
broadcastIntent.putExtra(CURFEW, curfew_config.isChecked());
broadcastIntent.putExtra(CURFEWSTARTTIME, curfew_start_time);
broadcastIntent.putExtra(CURFEWENDTIME, curfew_end_time);
startService(broadcastIntent);
}
UPDATE: Here is some code that demonstrates the problem:
Main activity:
package com.testapp.showbug;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import static com.testapp.showbug.BuildConfig.APPLICATION_ID;
public class MainActivity extends AppCompatActivity {
private Context context;
private Intent broadcastIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = getApplicationContext();
PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(APPLICATION_ID)) {
// Ask user to allow app to not optimize battery life. This will keep
// the app running when the user puts it in the background by pressing
// the Power or Home button.
Intent intent = new Intent();
intent.setAction(
Settings.
ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" +
APPLICATION_ID));
startActivity(intent);
}
broadcastIntent = new Intent(context,
BroadcastService.class);
startService(broadcastIntent);
}
public void onDestroy() {
super.onDestroy();
stopService(broadcastIntent);
}
}
BroadcastService:
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import static android.content.pm.ServiceInfo.
FOREGROUND_SERVICE_TYPE_LOCATION;
public class BroadcastService extends Service {
private static final int ONE_MINUTE = 60000;
private int allowed_time = 30, tickCounter;
private CountDownTimer countDown;
private NotificationManagerCompat notificationManager;
private NotificationCompat.Builder notification;
@Override
public void onCreate() {
super.onCreate();
// Clear all notifications sent earlier.
notificationManager =
NotificationManagerCompat.from(this);
notificationManager.cancelAll();
createNotificationChannel();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags,
int startId) {
if (intent == null) return START_STICKY;
Intent notificationIntent = new Intent(this,
BroadcastService.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0,
notificationIntent, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification = new
NotificationCompat.Builder(this,
getString(
R.string.default_notification_channel_id))
.setContentTitle(
getText(R.string.notification_title))
.setContentText(
getText(R.string.notification_message))
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(
getText(R.string.notification_message)))
.setContentIntent(PendingIntent.getActivity(
this, 0, new Intent(), 0))
.setSmallIcon(R.mipmap.ic_launcher_round)
.setLocalOnly(true)
.setContentIntent(pendingIntent);
} else {
notification = new
NotificationCompat.Builder(this,
getString(
R.string.default_notification_channel_id))
.setContentTitle(
getText(R.string.notification_title))
.setContentText(
getText(R.string.notification_message))
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(
getText(R.string.notification_message)))
.setContentIntent(PendingIntent.getActivity(
this, 0, new Intent(), 0))
.setSmallIcon(R.mipmap.ic_launcher_round)
.setContentIntent(pendingIntent);
}
startForeground(FOREGROUND_SERVICE_TYPE_LOCATION, notification.build());
tickCounter = -1;
// Start countdown timer for allowed time.
countDown = new CountDownTimer(allowed_time * ONE_MINUTE, ONE_MINUTE) {
@Override
public void onTick(long millisUntilFinished) {
tickCounter++;
Toast.makeText(getApplicationContext(), "tickCounter = " + tickCounter, Toast.LENGTH_LONG).show();
}
@Override
public void onFinish() {
Toast.makeText(getApplicationContext(), "tickCounter = " + allowed_time, Toast.LENGTH_LONG).show();
}
}.start();
return START_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.channel_name);
String description = getString(R.string.channel_description);
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(getString(R.string.default_notification_channel_id), name, importance);
channel.setDescription(description);
notificationManager.createNotificationChannel(channel);
}
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}
The above code starts a foreground service which in turn starts a CountDownTimer that increments the number of ticks by one every minute and prints the result. After 30 minutes, it should show a count of 30 ticks. Instead, it stops early, usually after 15-16 ticks.
Here's how to run the code:
Thanks for any help on this. It looks to me like a bug in the Android SDK, unlikely as that may seem. I don't see any other cause. By the way, I tested this code on a Pixel 2 and a Samsung Tab A, both running Android 11 (the only devices I own), so I don't know if the bug occurs on earlier versions of Android or different devices.
One way to fix this would be to turn off the feature from the app settings. It's the “let the app run in the background” option. Disabling this feature stops the app from going to sleep, thus not logging out the user. Open the SETTINGS app.
In order to make Android allow apps to run in background, all you need to do is press the open padlock icon right next to them. Once the open padlock changes and you get the “Locked” pop-up notification on your screen, you're all set!
First, swipe down once from the top of the screen and tap the gear icon. Scroll down and find “Apps.” Next, tap the three-dot menu icon and select “Special Access.” If you don't see it, there will be a section on that screen titled “Special App Access.” Now select “Optimize Battery Usage.”
Open the app that’s not working in the background from the list of apps. Tap Battery. Disable Battery optimization. Navigate again to Settings > Apps. Open the app that requires a network connection in order to work in the background.
Process to see what Android apps are currently running in the background involves the following steps- Go to your Android's “Settings” Scroll down. Find and select “About Phone” Scroll down to the "Build number" heading Tap the "Build number" heading seven times – Content write Tap the "Back" button ...
It moved Samsung to the top of the list following the roll-out of One UI 3.0, its own version or skin of Android 11. DontKillMyApp says the problem of phone manufacturers killing background apps has come about as a result of phones becoming more powerful but battery capacity failing to keep pace.
YouTube for Android doesn’t support background play with the minimized app or picture-in-picture without the premium account. However, if you have a premium account and YouTube is still not working in the background, make sure to follow the steps below. Enable Background Play and PiP.
I finally solved the problem, using wakelocks. Wakelocks ensure that the CPU continues to run after the Power button has been pressed. All I had to do was add the following code in BroadcastService.java:
In onCreate():
PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PeriSecure:MyWakeLock");
In onStartCommand():
wakeLock.acquire(allowed_time * ONE_MINUTE);
In onDestroy():
wakeLock.release();
That's all! The background Service runs as is should now.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With