Since I have always been responsible for the development of Android Telephony, I have not carefully studied the Telecom services and UI in the upper part of the communication process. I happened to encounter a call problem recently, which involved the Telecom part, so I took the time to carefully study the relevant code. Here is a simple summary. This article mainly contains the following two parts:
- What is Telecom service? What is its function?
- The startup and initialization process of Telecom module;
The next article mainly takes the actual call process as an example to analyze how telephony sends the phone information to the Telecom module after receiving the call and how Telecom handles the incoming calls.
What is Telecom Service
Telecom is a system service of Android. Its main function is to manage the current calls of the Android system, such as caller ID, answering calls, and hanging up calls, which plays a bridge between the Telephony module and the upper UI. For example, when Telephony receives a new incoming call, it will first inform Telecom, and then the Telecom service will notify the upper-layer application of incoming call information and display the incoming call interface.
Telecom service provides an interface class TelecomManager. Through the interface provided by it, the client can query the call status, send call requests, and add call links.
Judging from the corresponding files of the Telecom process, the user ID of the Telecom process is the same as the user ID of the system process, and is the core service of the system. So, what does the property value of android:process="system" mean? Check the official document, this means that Telecom will be started in the process system, so that resource sharing can be shared with other processes (for the global process of Android, it is the process where SystemServer is located).
android:process
By setting this attribute to a process name that's shared with another application, you can arrange for components of both applications to run in the same process — but only if the two applications also share a user ID and be signed with the same certificate.
If the name assigned to this attribute begins with a colon (‘:'), a new process, private to the application, is created when it's needed. If the process name begins with a lowercase character, a global process of that name is created. A global process can be shared with other applications, reducing resource usage.
<manifest xmlns:andro xmlns:androidprv="/apk/prv/res/android" package="" android:versionCode="1" android:versionName="1.0.0" coreApp="true" android:sharedUserId=""> <application android:label="@string/telecommAppLabel" android:icon="@mipmap/ic_launcher_phone" android:allowBackup="false" android:supportsRtl="true" android:process="system" android:usesCleartextTraffic="false" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true"> .... // Includes TelecomService <service android:name="." android:singleUser="true" android:process="system"> <intent-filter> <action android:name="" /> </intent-filter> </service> .... </application> </manifest>
Code path:
/android/applications/sources/services/Telecomm/
/android/frameworks/base/telecomm/
After understanding what Telecom service is, let’s take a look at how the Telecom service is started and initialized.
Starting and initializing the Telecom process
After the SystemServer process is initialized and started the system's core services such as ActivityManagerService, other services in the system will be loaded, which includes a system service related to the startup of the Telecom service specifically for loading Telecom:
private void startOtherServices() { .... //Start TelecomLoaderService system service to load Telecom (); // Start the telephony registration service to register the interface that listens to telephony status telephonyRegistry = new TelephonyRegistry(context); ("", telephonyRegistry); }
Call the system service manager's interface startService to create a new service, register it in the system, and finally call onStart() to start the service.
public class SystemServiceManager { @SuppressWarnings("unchecked") public SystemService startService(String className) { final Class<SystemService> serviceClass; try { serviceClass = (Class<SystemService>)(className); } catch (ClassNotFoundException ex) { .... } return startService(serviceClass); } // The service's class file is used to create a new service object (the service must inherit SystemService) @SuppressWarnings("unchecked") public <T extends SystemService> T startService(Class<T> serviceClass) { try { final String name = (); (TAG, "Starting " + name); (Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name); // Create the service. if (!(serviceClass)) { throw new RuntimeException("Failed to create " + name + ": service must extend " + ()); } final T service; try { Constructor<T> constructor = (); service = (mContext); } catch (InstantiationException ex) { throw new RuntimeException("Failed to create service " + name + ": service could not be instantiated", ex); } .... // Register it. (service); // Start it. try { (); } catch (RuntimeException ex) { throw new RuntimeException("Failed to start service " + name + ": onStart threw an exception", ex); } return service; } finally { (Trace.TRACE_TAG_SYSTEM_SERVER); } } }
Create a TelecomLoaderService system service and inform PackageManagerService (PMS) of the system's default SMS application, dial-up application, and SIM call management application (I don't know what the hell is this) so that the application can be found at the appropriate time.
public class TelecomLoaderService extends SystemService { ... public TelecomLoaderService(Context context) { super(context); mContext = context; registerDefaultAppProviders(); } @Override public void onStart() { } private void registerDefaultAppProviders() { final PackageManagerInternal packageManagerInternal = ( ); // Set a callback for the package manager to query the default sms app. ( new () { @Override public String[] getPackages(int userId) { synchronized (mLock) { .... ComponentName smsComponent = ( mContext, true); if (smsComponent != null) { return new String[]{()}; } return null; } }); // Set a callback for the package manager to query the default dialer app. ( new () { @Override public String[] getPackages(int userId) { synchronized (mLock) { .... String packageName = (mContext); if (packageName != null) { return new String[]{packageName}; } return null; } }); // Set a callback for the package manager to query the default sim call manager. ( new () { @Override public String[] getPackages(int userId) { synchronized (mLock) { .... TelecomManager telecomManager = (TelecomManager) (Context.TELECOM_SERVICE); PhoneAccountHandle phoneAccount = (userId); if (phoneAccount != null) { return new String[]{().getPackageName()}; } return null; } }); } }
So far, it seems that the Telecom service has not started, so where has the Telecom service been started? Take a closer look at the source code of TelecomLoaderService, which has an onBootPhase function, which is used by SystemServer to inform the system service of the current stage of system startup. Here you can see that after the (ActivityManagerService) AMS is started, you can start connecting to the Telecom service:
- First, register the default application (SMS/Dialer etc) notification object so that these applications can send changes (such as when a third-party SMS application is downloaded, the system can be notified of this change);
- Next, the registered operator configures a changed broadcast receiver, and the system will receive a notification if the configuration changes;
- Bind TelecomService and register it to the system.
public class TelecomLoaderService extends SystemService { private static final ComponentName SERVICE_COMPONENT = new ComponentName( "", ""); private static final String SERVICE_ACTION = ""; // The current system startup stage @Override public void onBootPhase(int phase) { if (phase == PHASE_ACTIVITY_MANAGER_READY) { registerDefaultAppNotifier(); registerCarrierConfigChangedReceiver(); connectToTelecom(); } } //Bind Telecom service private void connectToTelecom() { synchronized (mLock) { if (mServiceConnection != null) { // TODO: Is unbinding worth doing or wait for system to rebind? (mServiceConnection); mServiceConnection = null; } TelecomServiceConnection serviceConnection = new TelecomServiceConnection(); Intent intent = new Intent(SERVICE_ACTION); (SERVICE_COMPONENT); int flags = Context.BIND_IMPORTANT | Context.BIND_FOREGROUND_SERVICE | Context.BIND_AUTO_CREATE; // Bind to Telecom and register the service if ((intent, serviceConnection, flags, )) { mServiceConnection = serviceConnection; } } } }
Service binding:/guide/components/
Add the service to the ServiceManager and reconnect if the Telecom service connection is interrupted:
public class TelecomLoaderService extends SystemService { private class TelecomServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { // Normally, we would listen for death here, but since telecom runs in the same process // as this loader (process="system") thats redundant here. try { (new () { @Override public void binderDied() { connectToTelecom(); } }, 0); (mContext, false); //Add Telecom service (Context.TELECOM_SERVICE, service); .... } @Override public void onServiceDisconnected(ComponentName name) { connectToTelecom(); } } }
When binding the service, call the onBind interface of TelecomService to initialize the entire Telecom system and return an IBinder interface:
/** * Implementation of the ITelecom interface. */ public class TelecomService extends Service implements { @Override public IBinder onBind(Intent intent) { // Initialize the entire Telecom system initializeTelecomSystem(this); //Return to IBinder interface synchronized (getTelecomSystem().getLock()) { return getTelecomSystem().getTelecomServiceImpl().getBinder(); } } }
The main task of initializing the Telecom system is to create a new TelecomSystem class. In this class, the relevant classes of the entire Telecom service will be initialized:
static void initializeTelecomSystem(Context context) { if (() == null) { final NotificationManager notificationManager = (NotificationManager) (Context.NOTIFICATION_SERVICE); // Used to obtain contacts contactInfoHelper = new ContactInfoHelper(context); // Create a new singleton pattern object (new TelecomSystem(....)); } .... } }
Construct a singleton TelecomSystem object:
public TelecomSystem( Context context, /* User missed call notifications (excluding calls that have been answered or rejected) */ MissedCallNotifierImplFactory missedCallNotifierImplFactory, /* Query incoming call information */ CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, /* Headphone access status monitoring */ HeadsetMediaButtonFactory headsetMediaButtonFactory, /* Distance sensor management */ ProximitySensorManagerFactory proximitySensorManagerFactory, /* Telephone management during calls */ InCallWakeLockControllerFactory inCallWakeLockControllerFactory, /* Audio service management */ AudioServiceFactory audioServiceFactory, /* Bluetooth device management */ BluetoothPhoneServiceImplFactory bluetoothPhoneServiceImplFactory, BluetoothVoIPServiceImplFactory bluetoothVoIPServiceImplFactory, /* Query all timeout information */ timeoutsAdapter, /* Ring playback */ AsyncRingtonePlayer asyncRingtonePlayer, /* Phone number help class */ PhoneNumberUtilsAdapter phoneNumberUtilsAdapter, /* Block notification during call */ InterruptionFilterProxy interruptionFilterProxy) { mContext = (); // Initialize telecom-related features (mContext); // Initialize telecom's database (mContext); // Create a PhoneAccount registration management class mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext); .... // Initialize the call housekeeper, which is responsible for interacting with the upper UI mCallsManager = new CallsManager( mContext, mLock, mContactsAsyncHelper, callerInfoAsyncQueryFactory, mMissedCallNotifier, mPhoneAccountRegistrar, headsetMediaButtonFactory, proximitySensorManagerFactory, inCallWakeLockControllerFactory, audioServiceFactory, bluetoothManager, wiredHeadsetManager, systemStateProvider, defaultDialerAdapter, timeoutsAdapter,AsyncRingtonePlayer, phoneNumberUtilsAdapter, interruptionFilterProxy); (mCallsManager); // Register the broadcasts you need to receive (mUserSwitchedReceiver, USER_SWITCHED_FILTER); (mUserStartingReceiver, USER_STARTING_FILTER); (mFeatureChangedReceiver, FEATURE_CHANGED_FILTER); (mEmergencyReceiver, EMERGENCY_STATE_CHANGED); .... // Transfer stations for all incoming and outgoing calls mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager); // Create a TelecomServiceImpl interface to call TelecomService mTelecomServiceImpl = new TelecomServiceImpl( mContext, mCallsManager, mPhoneAccountRegistrar, new (), new UserCallIntentProcessorFactory() { @Override public UserCallIntentProcessor create(Context context, UserHandle userHandle) { return new UserCallIntentProcessor(context, userHandle); } }, defaultDialerAdapter, new (), mLock); // Perform specific initialization operations initialize(mContext); } }
What exactly does PhoneAccount in Android Telephony play? According to the instructions in the source code, PhoneAccount represents different ways of answering or making calls. For example, users can make calls through SIM cards, make video calls, or an emergency call, or even dial through the internal interface of telephony. Android distinguishes these ways of calling through PhoneAccount. A corresponding class PhoneAccountHandle is used to indicate which user is using the call service.
At this point, the entire Telecom service is started, so that the Telecom service can handle incoming or outgoing calls. In the next article, we will analyze how the power is passed and processed in Telecom and then sent to the upper UI interface.