Hiding foreground services notifications in Android
In this article, Mathieu explains how to obtain a foreground process without a permanent user notification.
In Android, Foreground Services are processes that need to run in the background, when the application is not visible and must not be killed by the system. These processes usually interact directly with the end user, as in music players. However, creating a foreground process requires the permanent display of a notification. While this is not an issue for music players, since the notification just provides song information and controls, in some cases, notifications are of no use, and in fact annoy users.
For the purposes of this article, we’ll assume that you know all about Android development, foreground services, broadcast receivers and notification channels. We will show you how to hide foreground service notifications on different versions of Android. There are three scenarios:
- Hiding notifications in two clicks in Android 8 and above.
- Automatically hiding notifications in Android 4 to 7.0.1.
- Minimizing notification visibility in Android 7.1.
Since there is no effective way of hiding notifications in Android 7.1, we will see what we can do instead.
Android 8 and above
In Android 8, it is not possible to automatically hide a foreground service notification; however, we can enable users to hide it manually in just two clicks.
Android 8 has introduced the concept of notification channels, making it possible to configure the notifications of a channel from the application information interface. You can configure the vibration, ringtone and visibility of notifications. In our case, we want to access the ability to show or hide notifications from a channel.
The idea is to create a specific channel for the foreground service notification, and send users directly to the notification channel configuration interface when they click on the notification.
To open the notification channel configuration interface, use a standard intent, such as the one below:
Intent i = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
i.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
i.putExtra(Settings.EXTRA_CHANNEL_ID, "serviceNotificationChannelId");
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
However, we can’t launch this intent directly when the notification is clicked, since it will crash the Android settings application. To avoid this, we can use the intent from our application process. We will create a broadcast receiver that will launch the notification channel settings, calling this broadcast receiver from our notification.
Here is the code for the broadcast receiver:
public class NotificationBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName())
.putExtra(Settings.EXTRA_CHANNEL_ID, HiddenService.SERVICE_NOTIFICATION_CHANNEL)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
Note that the flag FLAG_ACTIVITY_NEW_TASK
is required when launching an activity from a broadcast receiver.
Don’t forget to update your manifest with:
<receiver android:name=".NotificationBroadcastReceiver"/>
Now, we have to update the notification used for the foreground service to call this broadcast receiver when clicked. Here is the complete code to create the notification and set the service in the foreground:
NotificationManager notifManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel serviceChannel = new NotificationChannel(
"serviceNotificationChannelId",
"Hidden Notification Service",
NotificationManager.IMPORTANCE_DEFAULT);
notifManager.createNotificationChannel(serviceChannel);
Intent intent = new Intent(this, NotificationBroadcastReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
this,
1,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
);
Notification notification = new Notification.Builder(this, "serviceNotificationChannelId")
.setContentTitle("Hide Notification Example")
.setContentText("To hide me, click and uncheck \"Hidden Notification Service\"")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.getNotification();
startForeground(SERVICE_NOTIFICATION_ID, notification);
Here is the notification as displayed on the user’s phone; as instructed in the notification, the user just has to click on the notification.
The configuration interface will appear, and the user just has to uncheck “Show notifications”.
This is not an automatic process, but it is very effective, takes 2 seconds and requires no knowledge from the user.
Android 7.0 and below
In Android 7.0 and below, the code is a little bit trickier, but it requires no action from the user. The solution consists in exploiting the poor handling of foreground services and their notifications by Android.
Here, the trick is to create two services that start in the foreground with the same foreground id, then to stop one of the services requesting to remove the notification. The notification will disappear, but the remaining service will continue to run and will remain flagged as a foreground process.
To achieve this, we’ll need an additional dummy service whose sole purpose is to start in the foreground and stop immediately.
Here is the code for the dummy service:
public class DummyService extends Service {
@Override
public void onCreate() {
super.onCreate();
Notification.Builder builder = new Notification.Builder(this);
this.startForeground(-1, builder.getNotification());
stopSelf();
}
@Override
public void onDestroy() {
stopForeground(true);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
Note the “-1”, which is the foreground id for our service. In your implementation, use the same foreground id you used for your main foreground service.
The parameter (true) in the stopForeground
method call indicates that the notification should be removed.
Don’t forget to add this line in your Manifest:
<service android:name=".DummyService"/>
Now, we just have to start this dummy service in the service we want to run in the foreground without notification. The best solution is to add the line below in the “onCreate”:
startService(new Intent(this, DummyService.class));
Don’t forget to set your main service in foreground before starting the dummy service.
To ensure your service is actually running in foreground mode, run the following adb command:
adb shell dumpsys activity services <service>
Where <service> is the class name (ex: com.example.myapp.MyForegroundService
).
Once executed, you should see a line with isForeground=true
, followed by the foreground id and notification information.
Android 7.1
As stated at the beginning of this article, there is no good solution for Android 7.1. Both strategies described above don’t work.
In Android 7.1, you can disable notifications for your app as in Android 8 and above. However, you can’t pick and choose which notifications to hide or to show; it’s an all-or-nothing deal. If this solution fits your needs, you can use the same solution described for Android 8 (minus the notification channels), and call the settings interfaces from the broadcast receiver with the following code:
Intent i = new Intent()
.setAction("android.settings.APP_NOTIFICATION_SETTINGS")
.putExtra("app_package", getPackageName())
.putExtra("app_uid", getApplicationInfo().uid)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
It will open the following interface:
If hiding all notifications is not an option for you, the only solution is to set the notification priority to a minimum, sending it at the very bottom of the list of notifications and reducing its visibility.