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());
}
}