对PhoneAccount的使用体会并不深,所以很多知识点漏了或者写的不清晰,后续get到新体会时再完善吧。内容显的杂乱,简单分了五部分:
1. PhoneAccount的作用
2. PhoneAccount包含的信息
3. PhoneAccount相关的属性
4. SIMs based phone accounts
5. 题外篇ConnectionService
1. PhoneAccount的作用
作为一名Android系统Telephony研发人员,我清楚拨打,接听电话都需要PhoneAccount。但是心里对PhoneAccount总是感到一些疑惑,因为平时接触到的PhoneAccount都是基于SIM的或者Sip的; 所以对PhoneAccount的理解一直局限在拨打/接听电话时,使用哪张SIM,哪个Sip帐号; 没能从Android 设计层面上一直注重的扩展性方面去理解PhoneAccount。主要还是自己Level不够; 另外可能也是一直系统研发,缺少第三方App研发的思维。
扯远了,赶紧回来!
从设计层面来理解,PhoneAccount有就像桥梁,起到了连接的作用。为什么呢?
Telecom内有call,audio routing等功能,还负责bind in-call UI, 所以对于想使用这些资源的App来说都应该构建一个PhoneAccount对象,并使用TelecomManager注册到Telecom中。因为PhoneAccount对象中有ConnectionService信息,Telecom通过ConnectionService就可以和App(类似TeleSerfvice.apk)通信。例如 TeleService.apk在启动时就通过调用TelecomAccountRegistry创建PhoneAccount,这些PhoneAccount对象内包含了TeleService.apk的包名”com.android.phone”,以及实现了ConnectionService的TelephonyConnectionService。
Telecom.apk和framework- telecom完成了PhoneAccount信息的注册,更新和提供接口的任务。
下面是源码PhoneAccount.java里面的注释虽然简单,但是很明了。
/**
* Represents a distinct method to place or receive a phone call. Apps which can place calls
* and want those calls to be integrated into the dialer and in-call UI should build an
* instance of this class and register it with the system using {@link TelecomManager}.
* <p>
* {@link TelecomManager} uses registered {@link PhoneAccount}s to present the user with
* alternative options when placing a phone call. When building a {@link PhoneAccount}, the app
* should supply a valid {@link PhoneAccountHandle} that references the connection service
* implementation Telecom will use to interact with the app.
*/
2. PhoneAccount包含的信息
每一个PhoneAccount创建时必须有一个PhoneAccountHandle作为唯一标识; 每个PhoneAccountHandle只包含两部分信息:
The unique identifier for a PhoneAccount. A **PhoneAccountHandle** is made of two parts:
The component name of the associated connection service.
A string identifier that is unique across PhoneAccountHandles with the same component name.
一部分是包含ConnectionService信息的component;
[例如]
private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT =
new ComponentName("com.android.phone",
"com.android.services.telephony.TelephonyConnectionService");
另外一部分是唯一标识,component不是唯一的,所以需要一个唯一标识。
[例如]
SIM based的PhoneAccount使用ICC卡的ID。
下面简单解释了PhoneAccount中包含的信息,其中一些,我们可以在手机setting的SIM cards或者Status菜单中看到。
//唯一标识Id
private final PhoneAccountHandle mAccountHandle;
//看了getAddress的注释,没看明白,一头雾水; 不过这个应该是显示用的,当我们使用这个PhoneAccount
//拨打电话/来电时,这个信息可以用来显示。所以我们看到这个值可以用TelephonyManager.getLine1Number(int)
//来赋值,这个方法获取的值是通过TelephonyManager.setLine1NumberForDisplay设置的。
private final Uri mAddress;
//对于GSM,这个值是SIM卡里获取的MSISDN;对于CDMA,这个值是MDN。
private final Uri mSubscriptionAddress;
//用来标识当前PhoneAccount的使用性,比如是否可以拨打紧急电话(CAPABILITY_PLACE_EMERGENCY_CALLS)
private final int mCapabilities;
//颜色值,显示的时候会用到。
private final int mHighlightColor;
//这个不好解释,label就是label,存储一些容易辨识的信息 比如China Mobile等这类运营商的名称。
private final CharSequence mLabel;
//描述信息,比如用于Emergency call等。
private final CharSequence mShortDescription;
//支持的Uri,比如SIP, Tele, VoiceMail
private final List<String> mSupportedUriSchemes;
//支持的Audio通路, 比如扬声器,听筒,蓝牙等。
private final int mSupportedAudioRoutes;
//对应的图片
private final Icon mIcon;
//用来存储其他信息
private final Bundle mExtras;
//当前PhoneAccount是否可用
private boolean mIsEnabled;
//group Id是向Telecom中注册新PhoneAccount时用的。当注册新的PhoneAccount时,如果新PhoneAccount和旧
//PhoneAccount的group Id相同,并且connectionservice相同,那么新PhoneAccount会替代旧的PhoneAccount
//包括旧PhoneAccount的enable status。前提是就PhoneAccount是default PhoneAccount.
//可以参考PhoneAccountRegistrar.maybeReplaceOldAccount(PhoneAccount newAccount)方法。
private String mGroupId;
3. PhoneAccount相关的属性
PhoneAccount的属性有十几个, 但是下面三个属性不是很好理解。
CAPABILITY_CONNECTION_MANAGER:
和拥有这一属性的phoneAccount绑定的connectionService可以管理phone calls,包括使用其特有实现(比如VoIP call)去构造call对象,而非使用telephony stack。如果用户选择使用该PhoneAccount作为默认connection manager, 那么当用户选择使用传统的SIM-based telephony stack拨打电话时, 和这个PhoneAccount关联的ConnectionService会被优先使用。
可以管理SIM-based的call。
Flag indicating that this PhoneAccount can act as a connection manager for other connections. The ConnectionService associated with this PhoneAccount will be allowed to manage phone calls including using its own proprietary phone-call implementation (like VoIP calling) to make calls instead of the telephony stack.
When a user opts to place a call using the SIM-based telephony stack, the ConnectionService associated with this PhoneAccount will be attempted first if the user has explicitly selected it to be used as the default connection manager.
CAPABILITY_CALL_PROVIDER:
这个flag表示PhoneAccount可以构造phone call来代替传统的SIM-based telephony call。
当使用传统的SIM-based telephony stack拨打电话时,拥有这个flag的PhoneAccount都可以作为一种直观(不同的)方式。
该标志不同于CAPABILITY CONNECTION MANAGER,因为后者不允许管理或拨打来自built-in telephony stack 的call(这里的built-in telephony stack指什么?肯定不是SIM-based telephony stack吧,难道是第三放App内的独有实现?)。
Flag indicating that this PhoneAccount can make phone calls in place of traditional SIM-based telephony calls. This account will be treated as a distinct method for placing calls alongside the traditional SIM-based telephony stack. This flag is distinct from CAPABILITY_CONNECTION_MANAGER in that it is not allowed to manage or place calls from the built-in telephony stack.
CAPABILITY_SELF_MANAGED:
这个flag用于表示PhoneAccount会管理自己的Connections。这个flag适用于那些想使用Telecom framekwrok的call和audio routing功能,但又不想使用默认phone app(TeleService.apk) connection功能的独立应用。
self-managed ConnectionService需要为自己创建的Connections提供GUI。
/**
* Flag indicating that this {@link PhoneAccount} is responsible for managing its own
* {@link Connection}s. This type of {@link PhoneAccount} is ideal for use with standalone
* calling apps which do not wish to use the default phone app for {@link Connection} UX,
* but which want to leverage the call and audio routing capabilities of the Telecom framework.
*
* When set, {@link Connection}s created by the self-managed {@link ConnectionService} will not
* be surfaced to implementations of the {@link InCallService} API. Thus it is the
* responsibility of a self-managed {@link ConnectionService} to provide a user interface for
* its {@link Connection}s.
*
* Self-managed {@link Connection}s will, however, be displayed on connected Bluetooth devices.
Telecom/testapps这个例子展示了我们该如何使用self-manged connectionservice; 需要的权限,注册PhoneAccount,placeCall, 创建self-manged connectionservice等。
self-managed connectionservice是如何启动in-call UI的呢?
针对incoming call,android.telecom.Connection添加了onShowIncomingCallUi方法,在ConnectionService.createConnection方法中会调用onShowIncomingCallUi方法,这个方法内可以发生notification或者启动in-call UI.
针对out-going call仍然需求去bind 相应service, 不过只有包含了下面meta-data的service才可以被bind。
<meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS" android:value="true" />
注意
CAPABILITY_SELF_MANAGED 是不能和CAPABILITY_CALL_PROVIDER
CAPABILITY_CONNECTION_MANAGER以及CAPABILITY_SIM_SUBSCRIPTION同时使用的。
A self-managed android.telecom.PhoneAccount cannot also be a call provider.
A self-managed android.telecom.PhoneAccount cannot also be a SIM subscription.
A self-managed android.telecom.PhoneAccount cannot also be a connection manager.
4. SIMs based phone accounts
4.1 创建
TelecomAccountRegistry.setupOnBoot方法内注册了三个监听,当监听内容发生改变时会将之前的accounts销毁,并重新setup accounts。
- 通过SubscriptionManager监听SubscriptionInfo变化。
- 通过TelephonyManger监听service state变化。
- 通过context监听user switch。
/**
* Sets up all the phone accounts for SIMs on first boot.
*/
void setupOnBoot() {
// TODO: When this object "finishes" we should unregister by invoking
// SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener);
// This is not strictly necessary because it will be unregistered if the
// notification fails but it is good form.
// Register for SubscriptionInfo list changes which is guaranteed
// to invoke onSubscriptionsChanged the first time.
SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
mOnSubscriptionsChangedListener);
// We also need to listen for changes to the service state (e.g. emergency -> in service)
// because this could signal a removal or addition of a SIM in a single SIM phone.
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
// Listen for user switches. When the user switches, we need to ensure that if the current
// use is not the primary user we disable video calling.
mContext.registerReceiver(mUserSwitchedReceiver,
new IntentFilter(Intent.ACTION_USER_SWITCHED));
}
TelecomAccountRegistry.tearDownAccounts()方法负责清空之前的PhoneAccount数据,这里的PhoneAccount数据只是TelecomAccountRegistry中LinkedList类型成员变量mAccounts中的数据,而非存储到手机xml中的数据。
private void tearDownAccounts() {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
entry.teardown();
}
mAccounts.clear();
}
}
TelecomAccountRegistry.setupAccounts()方法负责创建新的PhoneAccount。setupAccounts()会根据phone 对象的数量(手机SIM卡的数量)来创建PhoneAccount。在创建的过程中,使用到了AccountEntry类,每创建一个PhoneAcount对象都需要创建一个AccountEntry对象。在AccountEntry的构造函数里我们发现每个AccountEntry对象会持有一个PstnIncomingCallNotifier对象。所以PstnIncomingCallNotifier和phone对象是一一对应的。
下面是setupAccounts方法, 这个方法注释很详细,直接拿来贴上。
private void setupAccounts() {
// Go through SIM-based phones and register ourselves -- registering an existing
// account will cause the existing entry to be replaced.
Phone[] phones = PhoneFactory.getPhones();
Log.d(this, "Found %d phones. Attempting to register.", phones.length);
final boolean phoneAccountsEnabled = mContext.getResources().getBoolean(
R.bool.config_pstn_phone_accounts_enabled);
synchronized (mAccountsLock) {
if (phoneAccountsEnabled) {
for (Phone phone : phones) {
int subscriptionId = phone.getSubId();
Log.d(this, "Phone with subscription id %d", subscriptionId);
// setupAccounts can be called multiple times during service changes.
//Don't add an account if the Icc has not been set yet.
if (subscriptionId >= 0 && phone.getFullIccSerialNumber() != null) {
mAccounts.add(new AccountEntry(phone, false /* emergency */,
false /* isDummy */));
}
}
}
// If we did not list ANY accounts, we need to provide a "default" SIM account
// for emergency numbers since no actual SIM is needed for dialing emergency
// numbers but a phone account is.
if (mAccounts.isEmpty()) {
mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(),
true /* emergency */, false /* isDummy */));
}
// Add a fake account entry.
if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
mAccounts.add(new AccountEntry(phones[0], false /* emergency */,
true /* isDummy */));
}
}
// Clean up any PhoneAccounts that are no longer relevant
cleanupPhoneAccounts();
// At some point, the phone account ID was switched from the subId to the iccId.
// If there is a default account, check if this is the case, and upgrade the
// default account from using the subId to iccId if so.
PhoneAccountHandle defaultPhoneAccount =
mTelecomManager.getUserSelectedOutgoingPhoneAccount();
ComponentName telephonyComponentName =
new ComponentName(mContext, TelephonyConnectionService.class);
if (defaultPhoneAccount != null &&
telephonyComponentName.equals(defaultPhoneAccount.getComponentName()) &&
!hasAccountEntryForPhoneAccount(defaultPhoneAccount)) {
String phoneAccountId = defaultPhoneAccount.getId();
if (!TextUtils.isEmpty(phoneAccountId) && TextUtils.isDigitsOnly(phoneAccountId)) {
PhoneAccountHandle upgradedPhoneAccount =
PhoneUtils.makePstnPhoneAccountHandle(
PhoneGlobals.getPhone(Integer.parseInt(phoneAccountId)));
if (hasAccountEntryForPhoneAccount(upgradedPhoneAccount)) {
mTelecomManager.setUserSelectedOutgoingPhoneAccount(upgradedPhoneAccount);
}
}
}
}
AccountEntry.registerPstnPhoneAccount方法做了具体的工作,获取构建PhoneAccount需要的数据,并将新构造的PhoneAccount对象注册到Telecom中。
/**
* Registers the specified account with Telecom as a PhoneAccountHandle.
*/
private PhoneAccount registerPstnPhoneAccount(boolean isEmergency, boolean isDummyAccount) {
String dummyPrefix = isDummyAccount ? "Dummy " : "";
// 首先创建一个PhoneAccountHandle对象。
// Build the Phone account handle.
PhoneAccountHandle phoneAccountHandle =
PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
mPhone, dummyPrefix, isEmergency);
// 下面开始准备创建PhoneAccount对象所需要的数据
// Populate the phone account data.
int subId = mPhone.getSubId();
String subscriberId = mPhone.getSubscriberId();
int color = PhoneAccount.NO_HIGHLIGHT_COLOR;
int slotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
String line1Number = mTelephonyManager.getLine1Number(subId);
if (line1Number == null) {
line1Number = "";
}
String subNumber = mPhone.getLine1Number();
if (subNumber == null) {
subNumber = "";
}
String label;
String description;
Icon icon = null;
//至于PhoneAccount中包含的信息,前面已经介绍过了,获取数据部分就省略了。
......
获取数据
......
PhoneAccount account = PhoneAccount.builder(phoneAccountHandle, label)
.setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, line1Number, null))
.setSubscriptionAddress(
Uri.fromParts(PhoneAccount.SCHEME_TEL, subNumber, null))
.setCapabilities(capabilities)
.setIcon(icon)
.setHighlightColor(color)
.setShortDescription(description)
.setSupportedUriSchemes(Arrays.asList(
PhoneAccount.SCHEME_TEL, PhoneAccount.SCHEME_VOICEMAIL))
.setExtras(instantLetteringExtras)
.setGroupId(groupId)
.build();
// 注册PhoneAccount
// Register with Telecom and put into the account entry.
mTelecomManager.registerPhoneAccount(account);
return account;
}
注册PhoneAccount使用的是TelecomManager.registerPhoneAccount方法,
经过TelecomServiceImpl.mBinderImpl.registerPhoneAccount方法后,最终会调用PhoneAccountRegistrar.registerPhoneAccount方法。这个方法会继续调用其他方法,将新PhoneAccount放进手机的“phone-account-registrar-state.xml”文件内; 这个文件在手机中的路径是”/data/user_de/0/com.android.server.telecom/”。
4.2 PhoneAccount在拨打和来电中的使用
4.2.1 来电
来电时,如果收到来电事件时没有一个可用的PhoneAccount,那么来电会被挂断。
在创建PhoneAccount时,PstnIncomingCallNotifier对象会被创建,该对象会调用phone对象的registerForNewRingingConnection方法注册来电监听,当有来电时PstnIncomingCallNotifier.mHandler会收到EVENT_NEW_RINGING_CONNECTION事件,处理方法是handleNewRingingConnection:
/**
* Verifies the incoming call and triggers sending the incoming-call intent to Telecom.
*
* @param asyncResult The result object from the new ringing event.
*/
private void handleNewRingingConnection(AsyncResult asyncResult) {
Log.d(this, "handleNewRingingConnection");
Connection connection = (Connection) asyncResult.result;
if (connection != null) {
Call call = connection.getCall();
// Final verification of the ringing state before sending the intent to Telecom.
if (call != null && call.getState().isRinging()) {
sendIncomingCallIntent(connection);
}
}
}
sendIncomingCallIntent方法尝试找到一个可用的PhoneAccount,然后将incoming事件传到Telecom;否则会挂断通话。
/**
* Sends the incoming call intent to telecom.
*/
private void sendIncomingCallIntent(Connection connection) {
Bundle extras = new Bundle();
if (connection.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED &&
!TextUtils.isEmpty(connection.getAddress())) {
Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, connection.getAddress(), null);
extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri);
}
// Specifies the time the call was added. This is used by the dialer for analytics.
extras.putLong(TelecomManager.EXTRA_CALL_CREATED_TIME_MILLIS,
SystemClock.elapsedRealtime());
PhoneAccountHandle handle = findCorrectPhoneAccountHandle();
if (handle == null) {
try {
connection.hangup();
} catch (CallStateException e) {
// connection already disconnected. Do nothing
}
} else {
TelecomManager.from(mPhone.getContext()).addNewIncomingCall(handle, extras);
}
}
findCorrectPhoneAccountHandle方法负责查找和phone对象关联的PhoneAccount。
4.2.2拨打电话
这部分涉及到的代码比较多,简单总结下吧。
拨打电话时有两处会选择PhoneAccount:
- CallsManger的startOutgoingCall(…)方法
这里会选择一个合适的PhoneAccount,并传进Call对象内。 - CreateConnectionProcessor的process()方法
当使用一个PhoneAccount创建connection失败时,这里会对所有可用的PhoneAccount进行尝试。
5. ConnectionService
An abstract service that should be implemented by any apps which either:
Can make phone calls (VoIP or otherwise) and want those calls to be integrated into the built-in phone app. Referred to as a system managed ConnectionService.
Are a standalone calling app and don’t want their calls to be integrated into the built-in phone app. Referred to as a self managed ConnectionService.
这里的built-in phone app指的应该是TeleService.apk, 而实现ConnectionService的app应该是指包含了类似TeleService.apk功能的apk。 这种apk不想使用TeleService.apk,但是想使用Telecom framework中的call 和audio routing功能,这种apk 创建的connection是不会显示在in-call ui的(会显示在蓝牙设备上),需要apk自己为用户提供GUI。