5

I'm trying to create an app that allows users to silence the device during set time intervals or manually. I would also like users to be able to enter more complex rules such as mute the phone only when connected to a certain WiFi network, Bluetooth device or when entering inside a geofence. In silent mode, however, I would like to allow users to create contact lists for which the phone should still ring.

To do this I created a foreground service in which I register a broadcast receiver. However, after only a couple of minutes since the activity leaves the foreground, the receiver stops working on time and updates from time to time.

This problem occurs in a device (Huawei/Honor 7x) with Android 8.

Note: I tried to add the app to the Doze whitelist and to disable vendor specific battery optimizations but nothing changed.

Below is an MCVE of my code. Whenever the receiver within the service is triggered, some int values are incremented and then they are displayed in the notification. On my device the values stop to increase after a couple of minutes since the activity leaves the foreground:

Manifest permissions

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>

Manifest declared service

<service android:name=".PhoneService"/>

Inside Activity to start Service

//for the sake of simplicity, I omit the code where runtime permissions are requested
Intent foregroundIntent = new Intent(this, PhoneService.class);
foregroundIntent.putExtra(PhoneService.EXTRA_ACTION, PhoneService.ACTION_START);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(foregroundIntent);
} else {
    startService(foregroundIntent);
}

Service class

public class PhoneService extends Service {

private static final String NOTIFICATION_CATEGORY = "notification_category";
private static final int NOTIFICATION_REQUEST_CODE = 0;
private static final String CHANNEL_ID = "test_channel";
private static final String CHANNEL_NAME = "test channel";
private static final int NOTIFICATION_ID = 1;
public static final String EXTRA_ACTION = "extra_action";
public static final String ACTION_START = "action_start";
public static final String ACTION_STOP = "action_stop";
private static final int STOP_ID = 2;
private int phoneReceiveCount = 0;
private int btReceiveCount = 0;
BroadcastReceiver phoneReceiver;

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();
    final IntentFilter intentFilter = new IntentFilter();
    //adding some filters
    intentFilter.addAction("android.intent.action.PHONE_STATE");
    intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
    intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    this.phoneReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            //update the count and show it in the notification body
            //used only to see if the receiver works
            String action = intent.getAction();
            if (action != null && action.equals("android.intent.action.PHONE_STATE")) {
                phoneReceiveCount++;
            } else if (action != null && (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) || action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                btReceiveCount++;
            }
            createNotification(context);
        }
    };
    registerReceiver(phoneReceiver, intentFilter);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    String action = intent.getStringExtra(EXTRA_ACTION);
    if (action.equals(ACTION_START)) {
        createNotification(this);
    } else if (action.equals(ACTION_STOP)) {
        stopSelf();
    }
    //also tried with START_STICKY
    return START_REDELIVER_INTENT;
}

@Override
public void onDestroy() {
    super.onDestroy();
    unregisterReceiver(phoneReceiver);
}

private void createNotification(Context context) {
    //intent to open app
    Intent entryActivityIntent = new Intent(context, MainActivity.class);
    entryActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    PendingIntent pendingEntryActivityIntent = PendingIntent.getActivity(context, NOTIFICATION_REQUEST_CODE, entryActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    //intent to stop foreground service
    Intent stopServiceIntent = new Intent(context, PhoneService.class);
    stopServiceIntent.putExtra(PhoneService.EXTRA_ACTION, PhoneService.ACTION_STOP);
    stopServiceIntent.addCategory(NOTIFICATION_CATEGORY);
    PendingIntent pendingStopForeground = PendingIntent.getService(context, STOP_ID, stopServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    //build notification
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_LOW);
        channel.enableVibration(false);
        channel.enableLights(false);
        notificationManager.createNotificationChannel(channel);
    }
    String contentText = "phoneReceiveCount: " + String.valueOf(phoneReceiveCount) + "\nbtReceiveCount: " + String.valueOf(btReceiveCount);
    NotificationCompat.Builder notificationBuilder =
            new NotificationCompat.Builder(context, CHANNEL_ID)
                    .setColor(ContextCompat.getColor(context, R.color.colorPrimary))
                    .setSmallIcon(R.drawable.ic_service_on)
                    .setContentTitle("This is the title")
                    .setContentText(contentText)
                    .addAction(R.drawable.ic_stop, "Stop", pendingStopForeground)
                    .setStyle(new NotificationCompat.BigTextStyle().bigText(contentText))
                    .setContentIntent(pendingEntryActivityIntent);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        notificationBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
    }
    startForeground(NOTIFICATION_ID, notificationBuilder.build());
}

}

1 Answers1

0

From Android 8 there are certain limitations on background services and broadcasts like broadcast receivers must be registered in runtime using context.registerReceiver.

Please see this link for detailed explanation:

https://developer.android.com/about/versions/oreo/background#broadcasts

You are targeting SDK above 25. Lowering that may solve the problem but its not recommended. Follow the guidelines and steps in the documentation to solve the issue.

  • Thanks, I'm aware of the limitations on broadcasts and background services in Android 8. I think that my receiver is correctly registered in runtime in onCreate method. Isn't it? – Emanuele Mazzante Jul 31 '18 at 15:20
  • 1
    I don't understand why, sometimes my broadcast receiver inside foreground service works and sometimes it doesnt – jashgopani Jun 02 '20 at 18:37
  • Does this mean we cannot register for a BroadcastReceiver inside of a Service? – IgorGanapolsky Oct 02 '20 at 00:45