Let’s take a look at 2 more UX conveniences for the Android caller application. First, let’s make sure that the app continues to function normally after minimizing or locking the screen with Android Foreground Services. After that, let’s see how we can implement direct links to a call or conference with Deep Links. By clicking on them, the smartphone users will be taken directly to the call.

How to create a Foreground Service on Android

Today’s smartphones and their operating systems have many built-in optimizations aimed at extending battery life. And mobile app developers need to keep in mind the potential actions the system can take on the app.

A prime example is freeing up resources and closing apps that the user is not actively interacting with at the moment. In this case, the system considers only the app that is currently displayed on the user’s screen to be “actively used”. All other running applications can be closed at any time if the system does not have enough resources for the actively used one. Thanks to this, we can open an infinite number of applications and not explicitly close them — the system will close the old ones, and when we return to them, the application will run again.

In general, this mechanism is convenient and necessary on mobile devices. But we want to bypass this restriction so that the call is protected from sudden closure by the system. Fortunately, it is possible to “mark” a part of the application as actively used, even if it is not displayed anymore. To do this, we use the Foreground Service. Note that even this does not give full protection from the system—but it increases the “priority” of the application in the eyes of the system and also allows you to keep some objects in memory even if `Activity` is closed.

Add permission to run such Android services:

 
 uses-permission android:name="android.permission.FOREGROUND_SERVICE" /
 

Let’s implement our service itself. In its simplest form it’s just a subclass Service, which has a link to our `CallManager` (so it won’t be cleaned up by garbage collector):

 
 class OngoingCallService : Service() {
    @Inject
    lateinit var abstractCallManager: AbstractCallManager
    // Implementation of an abstract method; we won’t use Bind so just return null 
    override fun onBind(intent: Intent): IBinder? = null
}
 

Service is an application component and, like Activity, must be specified in `AndroidManifest.xml`:

 
 service
    // Service class name
    android:name=".OngoingCallService"
    android:enabled="true"
    // This flag means that other applications can’t run this service
    android:exported="false"
    // Declare a type of our service
    android:foregroundServiceType="microphone|camera|phoneCall" /
 

Our Android Foreground Service starts up a bit differently than regular services:

 
 private fun startForegroundService() {
    val intent = Intent(this, OngoingCallService::class.java)
    ContextCompat.startForegroundService(this, intent)
}
 

On Android versions above 8, the Foreground Service must call the startForeground method within a few seconds, otherwise, the application is considered to be hung (ANR). It is necessary to pass a notification to this method because, for security reasons, the presence of such services should be visible to the user (if you do not know or have forgotten how to create notifications, you can refresh your memory in one of our previous articles about call notifications on Android):

 
 val notification = getNotification()startForeground(ONGOING_NOTIFICATION_ID, notification)
 

Everything that we wrote in the previous article about notifications applies to this notification — you can update it with the list of call participants, add buttons to it, or change its design completely. The only difference is that this notification will be `ongoing` by default and users won’t be able to “swipe” it.

On Android 13 and above POST_NOTIFICATIONS permission is required to display notifications. Declare it in the manifest:

 
 uses-permission android:name=”android.permission.POST_NOTIFICATIONS” />
 

You also need to request this permission at runtime, for example when entering the application. To learn more about requesting permissions, read the documentation.

If the user denies the notification permission, they still see notices related to foreground services in the Foreground Services (FGS) Task Manager but don’t see them in the notification drawer.

When the call is over, the service must be stopped, otherwise, the application can be completely closed only through the settings, which is very inconvenient for users. Our service is stopped in the same way as usual Android services:

 
 private fun stopForegroundService() { 
    val intent = Intent(this, OngoingCallService::class.java) 
    stopService(intent) 
} 
 

Starting and stopping a service is very convenient to implement if CallManager has a reactive field to monitor the status of the call, for example:

 
 abstractCallManager.isInCall
    .collect { if (it) startForegroundService() else stopForegroundService() }
 

This is the whole implementation of the service, which will allow to some extent protect our minimized application from being closed by the system.

Android Deep Links

An extremely user-friendly feature that simplifies the growth of the user base of the app is the links to a certain place in the app. If the user doesn’t have the app, the link opens a page on Google Play. In the context of call apps, the most successful use case is the ability to share a link to a call / meeting / room. The user wants to talk to someone, throws the link to the person he’s talking to, that person downloads the app, and then gets right into the call — what could be more convenient?

The links themselves to a particular location in the application are supported by the system without any additional libraries. But in order for the link to “survive” the installation of the application, we need to ask for help from Firebase Dynamic Links.

Let’s concentrate on the implementation of links handling in the application and leave their creation to backend developers.

So, the Android deep links with code examples. First, let’s add the library:

 
 dependencies {
    implementation 'com.google.firebase:firebase-dynamic-links:20.1.1'
}
 

To the user, deep links are ordinary links that he clicks on. But before opening a link in the browser, the system looks through the registry of applications and finds those that have declared that they handle links of this domain. If such an application is found – instead of opening in the browser, it launches the same application and the link is passed to it. If there is more than one such application – the system window will be shown with a list where the user can choose which application to open the link with. If you own the link domain, you can protect yourself from opening such links by other applications while yours is installed.

To declare the links that our app can handle, we need to add our `Activity` an intent-filter in `AndroidManifest.xml`:

 
 activity ...
    intent-filter
        // These action and category notify the system that we can “display” the links 
        action android:name="android.intent.action.VIEW"/
        category android:name="android.intent.category.DEFAULT"/
        category android:name="android.intent.category.BROWSABLE"/
        // Description of the link which we can handle. In this case these are the links starting from calls://forasoft.com 
        data
            android:host="forasoft.com"
            android:scheme="calls"/
    /intent-filter
/activity
 

When the user clicks the Dynamic Link and installs the application (or clicks on the link having the app already installed), the Activity will launch which is indicated as this link’s handler. In this Activity, we can get the link this way:

 
 Firebase.dynamicLinks
        .getDynamicLink(intent)
        .addOnSuccessListener(this) { data ->
            val deepLink: Uri? = data?.link
        }
 

When using regular deep links, the data becomes a bit simpler:

 
 val deepLink = intent?.data
 

That’s all, now all we have left is getting the parameters that interest us from the link and carrying out the actions in your application that are necessary to connect to the call:

 
         val meetindId = deepLink?.getQueryParameter("meetingid")
        if (meetingId != null) abstractCallManager.joinMeeting(meetingId)
 

Conclusion

In the final article of our cycle “what each application with calls should have” we’ve gone through keeping our application alive after minimizing it and using the deep links as a convenient option for invitations to the call. Now you know all the mechanisms that make the user experience better not only inside the application but also at the system level.

  • Development