12/07/2009
The Core of Android's Calling Experience: InCallService Explained
The InCallService is a fundamental component of the Android operating system responsible for managing and displaying the user interface for phone calls. If you're developing a dialer app, a VoIP service, or any application that interacts with telephony, understanding the InCallService is crucial. This service acts as the bridge between your application and the Android Telecom framework, allowing you to control and present call information to the user.

In essence, the InCallService is where your application takes over the role of handling calls, from the moment an incoming call rings to managing active conversations and call history. This article will delve into the inner workings of the InCallService, how to become the default phone app, and the APIs you'll use to create a seamless calling experience.
Becoming the Default Phone App
Android allows users to choose a default application for handling phone calls. To achieve this, your app must fulfill specific requirements:
- Handle ACTION_DIAL: Your app must provide a dial pad UI and respond to the {@code Intent.ACTION_DIAL} intent. This is the primary way users initiate outgoing calls through your app.
- Implement InCallService: You need to fully implement the {@link InCallService} API, providing both an incoming call UI and an ongoing call UI.
To request the {@code android.app.role.RoleManager#ROLE_DIALER} role, you would typically use the {@code RoleManager} API. This involves requesting the role and handling the result in your activity. If your app successfully obtains this role, it will be responsible for displaying the in-call UI when the device is not in car mode.
Important Note: If your InCallService implementation returns a null binding during the binding process, the Telecom framework will fall back to the system's default dialer. Your app should never return a null binding, as this indicates it doesn't meet the requirements for the {@code ROLE_DIALER}. Similarly, if your app's capabilities change at runtime and it no longer meets the role's requirements (e.g., disabling the declared InCallService), the {@code RoleManager} will remove your app from the role.
Manifest Registration for InCallService
To declare your {@code InCallService} in your app's manifest, you need to specify the {@code android.telecom.InCallService} action and declare necessary metadata. Here's an example:
<service android:name="your.package.YourInCallServiceImplementation" android:permission="android.permission.BIND_INCALL_SERVICE" android:exported="true"> <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" /> <meta-data android:name="android.telecom.IN_CALL_SERVICE_RINGING" android:value="true" /> <intent-filter> <action android:name="android.telecom.InCallService"/> </intent-filter> </service> Crucially, your {@code InCallService} must be declared with {@code android:exported="true"}. Setting it to {@code false} can prevent the Telecom framework from binding to your service.
Handling Incoming Calls
When your app receives a new incoming call via the {@link InCallService#onCallAdded(Call)} callback, you are responsible for displaying an incoming call UI. This is typically done using {@code android.app.NotificationManager} to post a notification. If you've declared {@code android.telecom.METADATA_IN_CALL_SERVICE_RINGING}, your app also handles playing the ringtone.
To play a ringtone, you'll create a {@code NotificationChannel} and configure its sound. You can use the default system ringtone or a custom audio resource.
The notification itself can include a {@code PendingIntent} that launches your full-screen incoming call UI. When the user is actively using the phone, this notification will appear as a heads-up notification. Otherwise, your full-screen UI will be presented.
Example of building an incoming call notification:
// Create an intent which triggers your fullscreen incoming call user interface. Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); intent.setClass(context, YourIncomingCallActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED); // Build the notification as an ongoing high priority item. final Notification.Builder builder = new Notification.Builder(context); builder.setOngoing(true); builder.setPriority(Notification.PRIORITY_HIGH); builder.setContentIntent(pendingIntent); builder.setFullScreenIntent(pendingIntent, true); builder.setSmallIcon(yourIconResourceId); builder.setContentTitle("Incoming Call"); builder.setContentText("From: John Doe"); // Add actions like 'Answer' or 'Reject' // builder.addAction(R.drawable.ic_answer, "Answer", answerPendingIntent); // builder.addAction(R.drawable.ic_reject, "Reject", rejectPendingIntent); NotificationManager notificationManager = context.getSystemService(NotificationManager.class); notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build()); Managing Active Calls
Once a call is active, the {@code InCallService} is responsible for displaying relevant information and providing controls. Key methods for managing calls include:
- {@link InCallService#getCalls()}: Retrieves the current list of active and held calls.
- {@link InCallService#canAddCall()}: Indicates whether the user can add another call to the current conference.
- {@link InCallService#onAudioStateChanged(AudioState)} / {@link InCallService#onCallAudioStateChanged(CallAudioState)}: Called when the audio state (e.g., speakerphone, Bluetooth) changes.
- {@link InCallService#setMuted(boolean)}: Allows your app to mute or unmute the microphone.
- {@link InCallService#setAudioRoute(int)} / {@link InCallService#requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}: Used to change the audio route (e.g., switch to speaker or Bluetooth). The latter is the modern API for managing call endpoints.
The {@code Call} object itself provides methods to manage call state, such as {@code answer()}, {@code reject()}, {@code disconnect()}, and {@code hold()}.
Video Calling with InCallService
The {@code InCallService} also supports video calls through the nested {@link VideoCall} class. Your implementation can:
- {@link VideoCall#setCamera(String)}: Select the camera for outgoing video.
- {@link VideoCall#setPreviewSurface(Surface)}: Set a surface for previewing the outgoing video.
- {@link VideoCall#setDisplaySurface(Surface)}: Set a surface for displaying incoming video.
- {@link VideoCall#sendSessionModifyRequest(VideoProfile)}: Request changes to the video session (e.g., upgrade to video, pause video).
- {@link VideoCall.Callback}: Implement callbacks to receive events from the {@code Connection.VideoProvider}, such as session modification requests from the peer or changes in video quality.
When a video call is active, the {@code InCallService} will receive callbacks related to video state changes, camera capabilities, and data usage.
Core Callbacks and Methods Summary
Here’s a quick overview of the essential methods and callbacks you'll interact with:
| Method/Callback | Description |
|---|---|
| {@code onBind(Intent)} | Returns the {@code IBinder} for the {@code InCallServiceBinder}. |
| {@code onUnbind(Intent)} | Called when the service is unbound. Cleans up resources. |
| {@code getCalls()} | Returns the list of current {@code Call} objects. |
| {@code canAddCall()} | Checks if another call can be added. |
| {@code onCallAdded(Call)} | Callback for new incoming or outgoing calls. |
| {@code onCallRemoved(Call)} | Callback when a call ends or is disconnected. |
| {@code onBringToForeground(boolean)} | Callback to bring the in-call UI to the foreground. |
| {@code onAudioStateChanged(AudioState)} | Callback for audio state changes (deprecated). |
| {@code onCallAudioStateChanged(CallAudioState)} | Callback for audio state changes (current API). |
| {@code onCallEndpointChanged(CallEndpoint)} | Callback when the audio output/input changes (e.g., Bluetooth speaker). |
| {@code onAvailableCallEndpointsChanged(List<CallEndpoint>)} | Callback when the available audio endpoints change. |
| {@code onMuteStateChanged(boolean)} | Callback when the mute state changes. |
| {@code setMuted(boolean)} | Sets the mute state. |
| {@code requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)} | Requests a change to the call audio endpoint. |
FAQ: Common Questions about InCallService
Q1: How do I make my app the default dialer?
A1: Your app must handle {@code Intent.ACTION_DIAL} and implement the {@link InCallService}. You then use the {@code RoleManager} to request the {@code android.app.role.RoleManager#ROLE_DIALER} role.
Q2: When should I use {@code InCallService} vs. {@code TelephonyManager}?
A2: {@code TelephonyManager} is for lower-level telephony operations and system-wide settings. {@code InCallService} is specifically for providing the in-call user interface and managing the active call experience.
Q3: How do I handle emergency calls?
A3: The system-provided dialer app is always used for emergency calls. If your app is the default dialer, ensure you use {@code TelecomManager.placeCall()} for emergency calls to guarantee a smooth user experience.
Q4: What is the difference between {@code onAudioStateChanged} and {@code onCallAudioStateChanged}?
A4: {@code onAudioStateChanged} is deprecated. {@code onCallAudioStateChanged} provides a more comprehensive view of the call's audio state, including details about supported Bluetooth devices and the current call endpoint.
Q5: How do I display the incoming call screen?
A5: When {@code onCallAdded()} is called for an incoming call, create a high-priority notification with a {@code PendingIntent} that launches your full-screen incoming call activity.
By mastering the {@code InCallService}, developers can create sophisticated and user-friendly calling applications that integrate seamlessly with the Android ecosystem.
If you want to read more articles similar to Understanding the Android In-Call Service, you can visit the Automotive category.
