Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

My Android 11 app stops running when the user puts it into the background unless the phone is connected to power

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:

enter image description here

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:

  1. Start the activity, disconnect the device from power (this is important and, obviously, requires a real device) and tap the Power button on the device.
  2. Wait 28 minutes.
  3. Put the app back into the foreground and wait for the next tick.
  4. Note that the next tick displays something less than 28.

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.

like image 703
FractalBob Avatar asked Apr 12 '21 16:04

FractalBob


People also ask

Why apps stop working in background?

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.

How do I force Android apps to run in the background?

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!

How do I stop apps from dying in the background?

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.”

How do I stop apps from running in the background Android?

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.

How do I see what apps are running 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 ...

Why are so many Android phones killing background apps?

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.

Why is YouTube not working in the background on Android?

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.


Video Answer


1 Answers

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.

like image 84
FractalBob Avatar answered Oct 15 '22 20:10

FractalBob