After Android has tightened permissions, how to use Telecom technology to build VOIP call applications and improve App users’ experience is believed to be one of the technical topics that developers are more concerned about. This article introduces how Rongyun uses Telecom to build VOIP call applications from the aspects of Telecom framework overview, Telecom startup process analysis, construction of VOIP call applications, and handling of common call scenarios.

1. Background and current situation

If you receive an IM voice call in the lower version of Android, you can jump to the call interface directly by starting Activity in the background; but when you receive an IM voice call on Android 10 and higher, you can only remind the user by popping up the notification bar. This method is not intuitive, and the call may be missed, resulting in poor user experience.

Because starting from Android 10, the system restricts the behavior of starting Activity from the background (component). This helps minimize the interruption to users, allows users to better control the content displayed on their screens, and increases user experience and security. The background application startup must be done through "display notification" to notify the user to start a new activity with the help of notification.

In this article, we focus on Android Telecom and the use of Telecom to build VOIP call applications. In the higher version of Android, the caller interface for IM voice calls can be redirected.

2. Overview of Telecom Framework

The Android Telecom framework manages audio and video calls on Android devices. These include SIM-based calls (for example, using the Telephony framework) and VOIP calls provided by the implementer of the ConnectionService API.

As shown in Figure 1, Telecom is responsible for the reporting of call status messages and the delivery of call control messages.
Image

Figure 1: Telecom message processing model

Call message delivery process:
After operating on the call interface, the user informs Telecom through the InCallAdapter (IInCallAdapter server implementation) object (finally calls CallsManager related functions), Telecom calls the IConnectionService encapsulation interface to initiate call management and control related RIL requests to RIL, and the RIL is converted into corresponding message sending Give Modem execution, including dialing, answering calls, rejecting calls, etc.

Call status update message reporting process:
RIL receives the change in the call status of the Modem, notifies the current Connection and Call status or property changes, and then after a series of Listener message callback processing, finally the ParcelableCall object is created by lnCallController and sent to the Dialer application using the llnCallService service interface call.

Telecom acts as an interaction bridge, interacting with InCallUI and Telephony [Phone process, TeleService]. The interaction process is shown in Figure 2:
Image

Figure 2: Interaction process between Telecom modules

In addition to the phone process and modem communicating through sockets, the processes interact through aidl. The Telecom process controls the phone process and the Incallui process through IConnectionService.aidl and IInCallService.aidl, respectively. The phone process (com.android.phone) and the Incallui process (com.android.incallui) notify the telecom process to be changed through IConnectionServiceAdapter.aidl and IInCallAdapter.aidl.

3. Analysis of Telecom startup process

· /frameworks/base/services/java/com/android/server/SystemServer.java
After the initialization of the SystemServer process is complete, start the TelecomLoaderService system service in startOtherServices, and load Telecom:
/**

  • Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
    */

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {

 ...

//Start the TelecomLoaderService system service for loading Telecom t.traceBegin("StartTelecomLoaderService");
mSystemServiceManager.startService(TelecomLoaderService.class);
t.traceEnd();
...
}

Swipe left to see more →

· /frameworks/base/services/core/java/com/android/server/telecom/TelecomLoaderService.java

The onBootPhase function in the TelecomLoaderService class is used by SystemServer to inform the system service of the current phase of the system startup. After the AMS (ActivityManagerService) startup is complete, start to connect to the Telecom service.
/**

  • Starts the telecom component by binding to its ITelecomService implementation. Telecom is setup
  • to run in the system-server process so once it is loaded into memory it will stay running.
  • @hide
    */

public class TelecomLoaderService extends SystemService {

    private static final ComponentName SERVICE_COMPONENT = new ComponentName(
            "com.android.server.telecom",
            "com.android.server.telecom.components.TelecomService");
    private static final String SERVICE_ACTION = "com.android.ITelecomService";
    // 当前系统启动的阶段
    @Override
    public void onBootPhase(int phase) {
        if (phase == PHASE_ACTIVITY_MANAGER_READY) {
            ...
            connectToTelecom();
        }
    }
    //绑定Telecom服务
    private void connectToTelecom() {
        synchronized (mLock) {
            if (mServiceConnection != null) {
                // TODO: Is unbinding worth doing or wait for system to rebind?
                mContext.unbindService(mServiceConnection);
                mServiceConnection = null;
            }
            TelecomServiceConnection serviceConnection = new TelecomServiceConnection();
            Intent intent = new Intent(SERVICE_ACTION);
            intent.setComponent(SERVICE_COMPONENT);
            int flags = Context.BIND_IMPORTANT | Context.BIND_FOREGROUND_SERVICE
                    | Context.BIND_AUTO_CREATE;
            // Bind to Telecom and register the service
            if (mContext.bindServiceAsUser(intent, serviceConnection, flags, UserHandle.SYSTEM)) {
                mServiceConnection = serviceConnection;
            }
        }
    }
}

Swipe left to see more →

· /packages/services/Telecomm/src/com/android/server/telecom/components/TelecomService.java

When binding the service, call the onBind interface of TelecomService to initialize the entire Telecom system and return to the IBinder interface.
/**

  • Implementation of the ITelecom interface.
    */

public class TelecomService extends Service implements TelecomSystem.Component {

@Override
public IBinder onBind(Intent intent) {
    Log.d(this, "onBind");
    return new ITelecomLoader.Stub() {
        @Override
        public ITelecomService createTelecomService(IInternalServiceRetriever retriever) {
            InternalServiceRetrieverAdapter adapter =
                    new InternalServiceRetrieverAdapter(retriever);

// Initialize the entire Telecom system

            initializeTelecomSystem(TelecomService.this, adapter);

//Return to IBinder interface

            synchronized (getTelecomSystem().getLock()) {
                return getTelecomSystem().getTelecomServiceImpl().getBinder();
            }
        }
    };
}

}

Swipe left to see more →

Telecom system initialization is mainly to create a new TelecomSystem class. In this class, all related classes of Telecom services are initialized.
/**

  • This method is to be called by components (Activitys, Services, ...) to initialize the
  • Telecom singleton. It should only be called on the main thread. As such, it is atomic
  • and needs no synchronization -- it will either perform its initialization, after which
  • the {@link TelecomSystem#getInstance()} will be initialized, or some other invocation of
  • this method on the main thread will have happened strictly prior to it, and this method
  • will be a benign no-op.
    *
  • @param context
    */

static void initializeTelecomSystem(Context context,

                                InternalServiceRetrieverAdapter internalServiceRetriever) {
if (TelecomSystem.getInstance() == null) {
    NotificationChannelManager notificationChannelManager =
            new NotificationChannelManager();
    notificationChannelManager.createChannels(context);

// Create a new TelecomSystem object in singleton mode

    TelecomSystem.setInstance(
            new TelecomSystem(...));
}

}

Swipe left to see more →

· /packages/services/Telecomm/src/com/android/server/telecom/TelecomSystem.java

Constructing a singleton TelecomSystem object will create classes related to the call. such as:

· CallsManager
· CallIntentProcessor
· TelecomServiceImpl

Fourth, build a VOIP call application

4.1 List statement and permissions

Declare a service that specifies the class used to implement the ConnectionService class in your application. The Telecom subsystem requires the service to declare the BIND_TELECOM_CONNECTION_SERVICE permission before it can be bound to it. The following example shows how to declare the service in the application manifest:
<service android:name="com.example.telecom.MyConnectionService"

android:label="com.example.telecom"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter>
    <action android:name="android.telecom.ConnectionService" />
</intent-filter>

</service>

Swipe left to see more →

In your application manifest, declare that your application uses the MANAGE_OWN_CALLS permission, as shown below:
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>

Swipe left to see more →

4.2 Realize the connection service

Your call application must provide an implementation of the ConnectionService class that the Telecom subsystem can bind to. Examples are as follows:
public class MyConnectionService extends ConnectionService {

@Override
public void onCreate() {
    super.onCreate();
}

@Override
public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
    super.onCreateIncomingConnection(connectionManagerPhoneAccount, request);
    MyConnection conn = new MyConnection(getApplicationContext());
    conn.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
    conn.setCallerDisplayName("Telecom", TelecomManager.PRESENTATION_ALLOWED);
    conn.setAddress(Uri.parse("tel:" + "10086"), TelecomManager.PRESENTATION_ALLOWED);
    conn.setRinging();
    conn.setInitializing();
    conn.setActive();
    return conn;
}

@Override
public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
    return super.onCreateOutgoingConnection(connectionManagerPhoneAccount, request);
}

}

Swipe left to see more →

4.3 Realizing the connection

Your application should create a subclass of Connection to represent incoming calls in the application. Examples are as follows:
public class MyConnection extends Connection {

MyConnection() {
}

/**
 * Telecom 子系统会在您添加新的来电时调用此方法,并且您的应用应显示其来电界面。
 */
@Override
public void onShowIncomingCallUi() {
    super.onShowIncomingCallUi();
    // 这里展示您自定义的通话界面
}

}

Swipe left to see more →

Five, handle common call scenarios

Take an incoming VOIP call as an example, follow the steps below:

  1. Register PhoneAccount (PhoneAccount is used to make and receive calls. Use TelecomManager to create a PhoneAccount. PhoneAccount has a unique identifier called PhoneAccountHandle, and Telecom will communicate with the App through the ConnectionService information provided by PhoneAccountHandle), as shown below:
    TelecomManager tm = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
    PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(

    new ComponentName(this.getApplicationContext(), MyConnectionService.class),
    "AppName");

    PhoneAccount phoneAccount = PhoneAccount.builder(phoneAccountHandle, "AppName").setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED).build();
    telecomManager.registerPhoneAccount(phoneAccount);

Swipe left to see more →

  1. Use the addNewIncomingCall(PhoneAccountHandle, Bundle) method to notify the Telecom subsystem when there is a new incoming call.
  2. The Telecom subsystem binds your application's ConnectionService implementation, and uses the onCreateIncomingConnection (PhoneAccountHandle, ConnectionRequest) method to request a new instance of the Connection class that represents a new incoming call.
  3. The Telecom subsystem uses the onShowIncomingCallUi() method to tell your application that it should display its incoming call interface.

Concluding remarks

For users on Android 10 and higher, the only way to receive IM instant messaging messages can be through the pop-up notification bar, which results in a poor user experience. Through the introduction of this article, I hope to help Android developers to improve the user experience.


融云RongCloud
82 声望1.2k 粉丝

因为专注,所以专业