#2 Floating Windows on Android: Foreground Service

Search for a command to run...

No comments yet. Be the first to comment.
Are you looking for a translation API to use in your new project? We curated a list of the 18 most popular translation service providers’ APIs available on the market. Maybe you’ll find a match! Translating your content online hasn’t ever been easier...

Learn why Spanish is an important language to consider and why it might be one of the first to choose when you start with the localization of your product. There’s no wonder Spanish is the most popular language on our platform! 📖 Introduction Before...

Learn why idioms are difficult to translate correctly, how to identify them in the text, and figure out their meaning to come up with the best translation in this article. Translators know very well that idioms aren't a piece of cake to translate. 🧁...

This short tutorial will help you set up a scalable Nuxt 3 boilerplate for small and large projects. Find out more about the configuration that Localazy uses for their website and build your next project on proven and solid ground. Nuxt 3 is nearing ...

Localazy can take complete care of your translation process with the built-in translation services available inside the platform. Forget the hassle of managing translation projects forever. All you need to do is choose the language and service. Learn...

vaclavhodek's blog
94 posts
Entrepreneur, idea maker, developer, saas & mobile enthusiast. Building developer-friendly localization solution at localazy.com.
Have you ever wondered how to make those floating windows used by Facebook Heads and other apps? Have you ever wanted to use the same technology in your app? It’s easy, and I will guide you through the whole process.
I'm the author of Floating Apps; the first app of its kind on Google Play and the most popular one with over 8 million downloads. After 6 years of the development of the app, I know a bit about it. It’s sometimes tricky, and I spent months reading documentation and Android source code and experimenting. I received feedback from tens of thousands of users and see various issues on different phones with different Android versions.
Here's what I learned along the way.
Before reading this article, it's recommended to go through Floating Windows on Android 1: Jetpack Compose & Room.
In this article, I teach you how to build the long-running foreground service that is necessary for floating windows and what are the limitations.
For the floating technology, it's necessary to have Service and not Activity. Android can have only one Activity in the foreground and so if we use Activity, other apps would be paused or restarted. And that's not the desired behavior - we want not to interrupt the current task in any way.
Standard Android services are not designed for long-running operations. They are rather designed to do a task in the background and finish.
To avoid our Service from being killed by the Android system, it's better to use a foreground service.
For our specific simple app, we use a service that is always running and renders a permanent notification. For your app, having the service running only when there are some floating windows active may be a better approach.
The magic is hidden in the overriding onStartCommand method and returning START_STICKY and START_NOT_STICKY correctly. The source code for this is shown below in this article.
A foreground service must show permanent/foreground notification shortly after the service is launched. If we fail to do so, the app is terminated.
On some devices, this may cause occasional crashes as the process may take a bit longer than the hard-coded interval.
Be sure to show the foreground notification as to the first thing. The source code for this is shown below in this article.
Also, some users simply dislike the permanent notification being shown, but there is a little we can do about it. They can, on some devices, hide the notification in the phone’s Settings.
On some phones and tablets, it's impossible to avoid the services from being killed thanks to the vendors who are integrating aggressive memory and process management.
There is an excellent website on this topic: Don't kill my app!
From Android API level 28, extra permission is necessary for foreground services. Add the line below to your AndroidManifest.xml:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
From Android O, a permanent notification is required, and with all the experience, I would recommend to use it for older versions too to prevent the service from being killed by Android.
The full source code for the foreground notification, including the code for stopping the service:
/**
* Remove the foreground notification and stop the service.
*/
private fun stopService() {
stopForeground(true)
stopSelf()
}
/**
* Create and show the foreground notification.
*/
private fun showNotification() {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val exitIntent = Intent(this, FloatingService::class.java).apply {
putExtra(INTENT_COMMAND, INTENT_COMMAND_EXIT)
}
val noteIntent = Intent(this, FloatingService::class.java).apply {
putExtra(INTENT_COMMAND, INTENT_COMMAND_NOTE)
}
val exitPendingIntent = PendingIntent.getService(
this, CODE_EXIT_INTENT, exitIntent, 0
)
val notePendingIntent = PendingIntent.getService(
this, CODE_NOTE_INTENT, noteIntent, 0
)
// From Android O, it's necessary to create a notification channel first.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
with(
NotificationChannel(
NOTIFICATION_CHANNEL_GENERAL,
getString(R.string.notification_channel_general),
NotificationManager.IMPORTANCE_DEFAULT
)
) {
enableLights(false)
setShowBadge(false)
enableVibration(false)
setSound(null, null)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
manager.createNotificationChannel(this)
}
} catch (ignored: Exception) {
// Ignore exception.
}
}
with(
NotificationCompat.Builder(
this,
NOTIFICATION_CHANNEL_GENERAL
)
) {
setTicker(null)
setContentTitle(getString(R.string.app_name))
setContentText(getString(R.string.notification_text))
setAutoCancel(false)
setOngoing(true)
setWhen(System.currentTimeMillis())
setSmallIcon(R.drawable.ic_launcher_foreground)
priority = Notification.PRIORITY_DEFAULT
setContentIntent(notePendingIntent)
addAction(
NotificationCompat.Action(
0,
getString(R.string.notification_exit),
exitPendingIntent
)
)
startForeground(CODE_FOREGROUND_SERVICE, build())
}
}
Our notification is permanent and cannot be canceled. It's clickable and when clicked, it invokes INTENT_COMMAND_NOTE command. Also, the notification has the exit action to invoke INTENT_COMMAND_EXIT.
As mentioned above, the magic behavior of the service is hidden inside onStartCommand. It's simple:
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val command = intent.getStringExtra(INTENT_COMMAND) ?: ""
// Exit the service if we receive the EXIT command.
// START_NOT_STICKY is important here, we don't want
// the service to be relaunched.
if (command == INTENT_COMMAND_EXIT) {
stopService()
return START_NOT_STICKY
}
// Be sure to show the notification first for all commands.
// Don't worry, repeated calls have no effects.
showNotification()
// Show the floating window for adding a new note.
if (command == INTENT_COMMAND_NOTE) {
Toast.makeText(
this,
"Floating window to be added in the next lessons.",
Toast.LENGTH_SHORT
).show()
}
return START_STICKY
}
For our service, we need a record in AndroidManifest.xml file.
<service
android:name=".FloatingService"
android:excludeFromRecents="true"
android:exported="false"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:stopWithTask="false" />
Explanation of parameters above:
android:excludeFromRecents - Don't show the service in Recent items screen.android:exported - There is no reason for the service to be accessible from outside the app.android:stopWithTask - Don't stop the service when the app is terminated, e.g. swiped out of the Recent items screen.For starting the service, let's create a small helper method. We need to handle the requirement for Android O and above - to use startForegroundService instead of startService.
fun Context.startFloatingService(command: String = "") {
val intent = Intent(this, FloatingService::class.java)
if (command.isNotBlank()) {
intent.putExtra(INTENT_COMMAND, command)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.startForegroundService(intent)
} else {
this.startService(intent)
}
}
In one of the following articles, we will learn how to start our service when the device boot, but for the time being, let's stick with starting the service when the main app is launched. For this, we just add a single line to our existing MainActivity's onCreate method.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Start the foreground service.
startFloatingService()
// ... the rest of the code ...
}
If your floating service is heavy and may cause occasional crashes, you may want to separate it from your app.
You can use android:multiprocess in your AndroidManifest.xml and separate your activities from the service by running them in a different process.
However, keep in mind that using more processes involves extra effort for synchronizing state as activities, and the service would no longer share memory.
Above, we added new strings notification_channel_general, notification_text and notification_exit, so be sure to run the Gradle task uploadStrings to upload your translations to the Localazy platform for translating.
A minute after I uploaded my 11 strings, 6 of them are ready in 80 languages!
More information on the importance of app localization was described in Floating Windows on Android 1: Jetpack Compose & Room.
The animation below demonstrates how the permanent notification is shown when the main app is launched and remains active even if the main app is removed from the Recent items screen.
The whole source code for this article is available on Github.
This article is part of the Floating Windows on Android series.