转载请注明出处:https://blog.csdn.net/turtlejj/article/details/81567426,谢谢~
最近由于碰到了一个CTS的问题,涉及到了PhoneAccount,然而之前并没有接触过相关的内容。因此想在网上找些资料来看,结果发现并没有特别详细介绍PhoneAccount的文章,于是萌生了自己写一片的想法。
废话不多说,现在就开始吧。
一、什么是PhoneAccount
/frameworks/base/telecom/java/android/telecom/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.
*/
以上是Android源码中对PhoneAccount的介绍,简单来说就是,PhoneAccount是用来接打电话的,我们可以使用TelecomManager里面的方法来创建一个PhoneAccount。同时,PhoneAccount还有一个唯一的标识,叫做PhoneAccountHandle,Telecom会通过PhoneAccountHandle所提供的ConnectionService信息来与App进行通信。
由于自己是做Android系统研发的,所以对第三方的App以及网络电话(Sip)不是特别了解,因此我们这里先已最常见的Sim卡类型的PhoneAccount来进行讲解。
我们的手机之所以可以通过Sim卡来拨打电话,同样也是因为系统会为每张当前插入手机的Sim卡都创建了一个PhoneAccount。Telecom通过Sim卡的PhoneAccount就可以与Dialer以及in-call UI进行通信,并成功完成电话的拨打以及接听。
那么下面我们就来想写介绍一下PhoneAccount以及PhoneAccountHandle。
二、PhoneAccountHandle详解
刚才说到了PhoneAccountHandle这个词,它到底是个什么东西呢?我们先来看代码
PhoneAccountHandle类有三个局部变量,分别是ComponentName,Id和UserHandle
/frameworks/base/telecomm/java/android/telecom/PhoneAccountHandle.java
public final class PhoneAccountHandle implements Parcelable {
private final ComponentName mComponentName;
private final String mId;
private final UserHandle mUserHandle;
......
public PhoneAccountHandle(
@NonNull ComponentName componentName,
@NonNull String id,
@NonNull UserHandle userHandle) {
checkParameters(componentName, userHandle);
mComponentName = componentName;
mId = id;
mUserHandle = userHandle;
}
......
}
下面我们一个一个来说:
1.ComponentName
ComponentName类的作用是用来指定一个应用组件,可指定的类型有Activity,Service,BroadcastReceiver或者ContentProvider。
我们知道,要想唯一地指定一个组件,需要包含这个组件的包名以及类名。同时也就是该类所包含的两个局部变量,mPackage和mClass。
对于我们拨打和接听电话而言,我们刚刚在最开始提到过,PhoneAccountHandle需要提供一个ConnectionService来使Telecom和App进行通信。因此,这里要指定的,应该是一个Service类型的组件。
/frameworks/base/core/java/android/content/ComponentName.java
/**
* Identifier for a specific application component
* ({@link android.app.Activity}, {@link android.app.Service},
* {@link android.content.BroadcastReceiver}, or
* {@link android.content.ContentProvider}) that is available. Two
* pieces of information, encapsulated here, are required to identify
* a component: the package (a String) it exists in, and the class (a String)
* name inside of that package.
*
*/
public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
private final String mPackage;
private final String mClass;
......
public ComponentName(@NonNull String pkg, @NonNull String cls) {
if (pkg == null) throw new NullPointerException("package name is null");
if (cls == null) throw new NullPointerException("class name is null");
mPackage = pkg;
mClass = cls;
}
......
}
2.Id
对于通过Sim卡创建的PhoneAccountHandle来说,id所存储的就是每张Sim卡的IccId,有的同学要问了,什么是IccId呢?
通俗的来讲,IccId就相当于我们每张Sim卡的身份证,是每张Sim卡的唯一标识。一般来说,我们中国运营商的Sim卡都是以8986XX为开头的20位数字串(有些特殊的卡会包含字母),例如。
其中,86代表中国(类似于打电话时,为什么中国号码前面都是+86)。而后面的XX,不同运营商对应着不同的值,其中移动卡可能为00 02 07等,联通卡可能为01 09等,电信卡可能为03 11等(每个运营商随着所发行的Sim卡数量的增多,为了保证IccId的唯一性,可能会添加号段)。
那么,使用PhoneAccountHandle使用IccId作为自己的id,就可以很大程度上的保证自己的唯一性,从而更好的作为PhoneAccount的唯一标识。
3. UserHandle
UserHandle又是什么呢?
众所周知,我们的Linux系统是支持多用户的。那么,我们的Android系统是基于Linux开发而来的,(原生的Android系统)同样也是支持多用户的。只不过,由于一些OEM厂商对Android系统进行了深度定制,有可能在Settings中把设置多用户的入口给取消掉了,因此,我们看不到,也无法设置多用户。不过一般我们都是自己一个人使用手机,因此,这个功能对于我们来说,其实也可有可无。
我们只需要知道,一般来说,我们的手机都会有一个userId为0的默认用户就可以了。
三、PhoneAccount详解
通过以上的讲解,相信大家已经对PhoneAccountHandle有了一个初步的了解。我们接下来,就开始介绍PhoneAccount。老样子,我们来看代码。
不难发现,PhoneAccount的构造方法是private类型的,也就是说,我们没办法直接调用其来构建PhoneAccount实例,那究竟该怎么创建PhoneAccount对象呢?这时,PhoneAccount的内部类Builder就派上了用场,不过,我们这里暂且先不提Builder的用法,等到后面讲解如何创建PhoneAccount时,再来细说。
/frameworks/base/telecomm/java/android/telecom/PhoneAccount.java
public final class PhoneAccount implements Parcelable {
......
// PhoneAccount的唯一标识
private final PhoneAccountHandle mAccountHandle;
// Scheme与line1Number拼接而成的Uri
private final Uri mAddress;
// Scheme与从卡中获取的MSISDN(或CDMA卡的MDN)拼接而成的Uri
private final Uri mSubscriptionAddress;
// 该PhoneAccount的所能支持的能力,后面会详细讲解
private final int mCapabilities;
// 高亮显示的颜色
private final int mHighlightColor;
// 该PhoneAccount的标签
private final CharSequence mLabel;
// 该PhoneAccount的简短描述
private final CharSequence mShortDescription;
// 所能支持的uriScheme
private final List<String> mSupportedUriSchemes;
// 所支持的AudioRoute,例如EARPIECE(听筒)、SPEAKER(扬声器)、WIRED_HEADSET(带话筒的耳机)、BLUETOOTH(蓝牙)
private final int mSupportedAudioRoutes;
// 图标
private final Icon mIcon;
private final Bundle mExtras;
// 是否可用
private boolean mIsEnabled;
// 组Id
private String mGroupId;
......
public static class Builder {
private PhoneAccountHandle mAccountHandle;
private Uri mAddress;
private Uri mSubscriptionAddress;
private int mCapabilities;
private int mSupportedAudioRoutes = CallAudioState.ROUTE_ALL;
private int mHighlightColor = NO_HIGHLIGHT_COLOR;
private CharSequence mLabel;
private CharSequence mShortDescription;
private List<String> mSupportedUriSchemes = new ArrayList<String>();
private Icon mIcon;
private Bundle mExtras;
private boolean mIsEnabled = false;
private String mGroupId = "";
......
/**
* Creates an instance of the {@link PhoneAccount.Builder} from an existing
* {@link PhoneAccount}.
*
* @param phoneAccount The {@link PhoneAccount} used to initialize the builder.
*/
public Builder(PhoneAccount phoneAccount) {
mAccountHandle = phoneAccount.getAccountHandle();
mAddress = phoneAccount.getAddress();
mSubscriptionAddress = phoneAccount.getSubscriptionAddress();
mCapabilities = phoneAccount.getCapabilities();
mHighlightColor = phoneAccount.getHighlightColor();
mLabel = phoneAccount.getLabel();
mShortDescription = phoneAccount.getShortDescription();
mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes());
mIcon = phoneAccount.getIcon();
mIsEnabled = phoneAccount.isEnabled();
mExtras = phoneAccount.getExtras();
mGroupId = phoneAccount.getGroupId();
mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes();
}
......
public PhoneAccount build() {
// If no supported URI schemes were defined, assume "tel" is supported.
if (mSupportedUriSchemes.isEmpty()) {
addSupportedUriScheme(SCHEME_TEL);
}
return new PhoneAccount(
mAccountHandle,
mAddress,
mSubscriptionAddress,
mCapabilities,
mIcon,
mHighlightColor,
mLabel,
mShortDescription,
mSupportedUriSchemes,
mExtras,
mSupportedAudioRoutes,
mIsEnabled,
mGroupId);
}
}
private PhoneAccount(
PhoneAccountHandle account,
Uri address,
Uri subscriptionAddress,
int capabilities,
Icon icon,
int highlightColor,
CharSequence label,
CharSequence shortDescription,
List<String> supportedUriSchemes,
Bundle extras,
int supportedAudioRoutes,
boolean isEnabled,
String groupId) {
mAccountHandle = account;
mAddress = address;
mSubscriptionAddress = subscriptionAddress;
mCapabilities = capabilities;
mIcon = icon;
mHighlightColor = highlightColor;
mLabel = label;
mShortDescription = shortDescription;
mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes);
mExtras = extras;
mSupportedAudioRoutes = supportedAudioRoutes;
mIsEnabled = isEnabled;
mGroupId = groupId;
}
public static Builder builder(
PhoneAccountHandle accountHandle,
CharSequence label) {
return new Builder(accountHandle, label);
}
}
PhoneAccount的成员变量比较多,我在代码里都添加了注释,不过,下面还是要讲解几个比较重要的变量。
1. mAddress和mSubscriptionAddress
其实这两个变量的具体含义我也不是特别清楚,不过对于SIM-based PhoneAccount来说,这两个变量的值相同,具体的值如下:
tel:%2B8613012345678
其中tel为Scheme,%2B是'+',后面的86是中国的国家码,最后面的13012345678就是我们的手机号码。
2. mCapabilities
这个变量从字面意思就很容易理解,代表这个PhoneAccount所具备的能力。其实际上就是一个bitMask,通过对不同的bit置位来代表其所具备的Capability。代码中定义的所有Capability都有详细的注释讲解,我就不一一进行解释了。
/frameworks/base/telecomm/java/android/telecom/PhoneAccount.java
/**
* Flag indicating that this {@code PhoneAccount} can act as a connection manager for
* other connections. The {@link ConnectionService} associated with this {@code 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.
* <p>
* When a user opts to place a call using the SIM-based telephony stack, the
* {@link ConnectionService} associated with this {@code PhoneAccount} will be attempted first
* if the user has explicitly selected it to be used as the default connection manager.
* <p>
* See {@link #getCapabilities}
*/
public static final int CAPABILITY_CONNECTION_MANAGER = 0x1;
/**
* Flag indicating that this {@code 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 {@link #CAPABILITY_CONNECTION_MANAGER} in that it is not allowed to manage
* or place calls from the built-in telephony stack.
* <p>
* See {@link #getCapabilities}
* <p>
*/
public static final int CAPABILITY_CALL_PROVIDER = 0x2;
/**
* Flag indicating that this {@code PhoneAccount} represents a built-in PSTN SIM
* subscription.
* <p>
* Only the Android framework can register a {@code PhoneAccount} having this capability.
* <p>
* See {@link #getCapabilities}
*/
public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4;
/**
* Flag indicating that this {@code PhoneAccount} is currently able to place video calls.
* <p>
* See also {@link #CAPABILITY_SUPPORTS_VIDEO_CALLING} which indicates whether the
* {@code PhoneAccount} supports placing video calls.
* <p>
* See {@link #getCapabilities}
*/
public static final int CAPABILITY_VIDEO_CALLING = 0x8;
/**
* Flag indicating that this {@code PhoneAccount} is capable of placing emergency calls.
* By default all PSTN {@code PhoneAccount}s are capable of placing emergency calls.
* <p>
* See {@link #getCapabilities}
*/
public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 0x10;
/**
* Flag indicating that this {@code PhoneAccount} is capable of being used by all users. This
* should only be used by system apps (and will be ignored for all other apps trying to use it).
* <p>
* See {@link #getCapabilities}
* @hide
*/
@SystemApi
public static final int CAPABILITY_MULTI_USER = 0x20;
/**
* Flag indicating that this {@code PhoneAccount} supports a subject for Calls. This means a
* caller is able to specify a short subject line for an outgoing call. A capable receiving
* device displays the call subject on the incoming call screen.
* <p>
* See {@link #getCapabilities}
*/
public static final int CAPABILITY_CALL_SUBJECT = 0x40;
/**
* Flag indicating that this {@code PhoneAccount} should only be used for emergency calls.
* <p>
* See {@link #getCapabilities}
* @hide
*/
public static final int CAPABILITY_EMERGENCY_CALLS_ONLY = 0x80;
/**
* Flag indicating that for this {@code PhoneAccount}, the ability to make a video call to a
* number relies on presence. Should only be set if the {@code PhoneAccount} also has
* {@link #CAPABILITY_VIDEO_CALLING}.
* <p>
* When set, the {@link ConnectionService} is responsible for toggling the
* {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE_VT_CAPABLE} bit on the
* {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE} column to indicate whether
* a contact's phone number supports video calling.
* <p>
* See {@link #getCapabilities}
*/
public static final int CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE = 0x100;
/**
* Flag indicating that for this {@link PhoneAccount}, emergency video calling is allowed.
* <p>
* When set, Telecom will allow emergency video calls to be placed. When not set, Telecom will
* convert all outgoing video calls to emergency numbers to audio-only.
* @hide
*/
public static final int CAPABILITY_EMERGENCY_VIDEO_CALLING = 0x200;
/**
* Flag indicating that this {@link PhoneAccount} supports video calling.
* This is not an indication that the {@link PhoneAccount} is currently able to make a video
* call, but rather that it has the ability to make video calls (but not necessarily at this
* time).
* <p>
* Whether a {@link PhoneAccount} can make a video call is ultimately controlled by
* {@link #CAPABILITY_VIDEO_CALLING}, which indicates whether the {@link PhoneAccount} is
* currently capable of making a video call. Consider a case where, for example, a
* {@link PhoneAccount} supports making video calls (e.g.
* {@link #CAPABILITY_SUPPORTS_VIDEO_CALLING}), but a current lack of network connectivity
* prevents video calls from being made (e.g. {@link #CAPABILITY_VIDEO_CALLING}).
* <p>
* See {@link #getCapabilities}
*/
public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 0x400;
/**
* 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.
* <p>
* 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.
* <p>
* Self-managed {@link Connection}s will, however, be displayed on connected Bluetooth devices.
*/
public static final int CAPABILITY_SELF_MANAGED = 0x800;
/**
* Flag indicating that this {@link PhoneAccount} is capable of making a call with an
* RTT (Real-time text) session.
* When set, Telecom will attempt to open an RTT session on outgoing calls that specify
* that they should be placed with an RTT session , and the in-call app will be displayed
* with text entry fields for RTT. Likewise, the in-call app can request that an RTT
* session be opened during a call if this bit is set.
*/
public static final int CAPABILITY_RTT = 0x1000;
/* NEXT CAPABILITY: 0x2000 */
一般来说,SIM-based PhoneAccount的mCapabilities值为1078,变为十六进制后为0x436,通过如下的代码可以知道,SIM-based PhoneAccount的Capability为
0x400 SUPPORTS_VIDEO_CALLING
0x20 MULTI_USER
0x10 PLACE_EMERGENCY_CALLS
0x4 SIM_SUBSCRIPTION
0x2 CALL_PROVIDER
3. mSupportedUriSchemes
mSupportedUriSchemes这个变量,是用来保存PhoneAccount所支持的UriScheme类型的。它就是一个用来存放String类型变量的List。我们可支持的UriScheme有三种tel(电话)、voicemail(语音信箱)和sip(网络电话)
/frameworks/base/telecomm/java/android/telecom/PhoneAccount.java
/**
* URI scheme for telephone number URIs.
*/
public static final String SCHEME_TEL = "tel";
/**
* URI scheme for voicemail URIs.
*/
public static final String SCHEME_VOICEMAIL = "voicemail";
/**
* URI scheme for SIP URIs.
*/
public static final String SCHEME_SIP = "sip";
SIM-based PhoneAccount一般支持tel和voicemail。
四、PhoneAccount的创建
通过前面的讲解,相信大家已经对PhoneAccountHandle以及PhoneAccount有了一个初步的了解了,那么下面我们来看一看,PhoneAccount是如何创建的。
在TelecomSystem类的构造方法中,会创建一个PhoneAccountRegistrar类的对象,而这个类就是我们创建关键维护PhoneAccount信息的关键所在。
查看PhoneAccountRegistrar类的构造方法,系统创建了一个文件,叫做"phone-account-registrar-state.xml",这个文件就是用来保存defaultPhoneAccountHandle以及PhoneAccount信息的。后面我们会给大家展示这个文件的内容。
接下来,系统又创建了一个State类的实例mState,它是用来保存实例化后的defaultPhoneAccountHandle和PhoneAccount信息的,通过mState,可以快速的获取到它们。
/packages/services/Telecomm/src/com/android/server/telecom/PhoneAccountRegistrar.java
public class PhoneAccountRegistrar {
......
private static final String FILE_NAME = "phone-account-registrar-state.xml";
......
@VisibleForTesting
public PhoneAccountRegistrar(Context context, String fileName,
DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
// 创建一个文件名为phone-account-registrar-state.xml的文件
mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
mState = new State();
mContext = context;
mUserManager = UserManager.get(context);
mDefaultDialerCache = defaultDialerCache;
mSubscriptionManager = SubscriptionManager.from(mContext);
mAppLabelProxy = appLabelProxy;
mCurrentUserHandle = Process.myUserHandle();
read();
}
......
@VisibleForTesting
public static class State {
/**
* Store the default phone account handle of users. If no record of a user can be found in
* the map, it means that no default phone account handle is set in that user.
*/
// 保存每个用户所设置的defaultPhoneAccountHandle
public final Map<UserHandle, DefaultPhoneAccountHandle> defaultOutgoingAccountHandles
= new ConcurrentHashMap<>();
/**
* The complete list of {@code PhoneAccount}s known to the Telecom subsystem.
*/
// 用来保存当前手机中的所有PhoneAccount信息
public final List<PhoneAccount> accounts = new CopyOnWriteArrayList<>();
/**
* The version number of the State data.
*/
public int versionNumber;
}
......
}
说过了PhoneAccount在哪里保存,接下来就要开始说明,我们的PhoneAccount到底是在哪里创建的了。
/packages/services/Telephony/src/com/android/services/telephony/TelecomAccountRegistry.java
final class TelecomAccountRegistry {
final class AccountEntry implements PstnPhoneCapabilitiesNotifier.Listener {
private final Phone mPhone;
private PhoneAccount mAccount;
private final PstnIncomingCallNotifier mIncomingCallNotifier;
private final PstnPhoneCapabilitiesNotifier mPhoneCapabilitiesNotifier;
private boolean mIsEmergency;
private boolean mIsDummy;
private boolean mIsVideoCapable;
private boolean mIsVideoPresenceSupported;
private boolean mIsVideoPauseSupported;
private boolean mIsMergeCallSupported;
private boolean mIsMergeImsCallSupported;
private boolean mIsVideoConferencingSupported;
private boolean mIsMergeOfWifiCallsAllowedWhenVoWifiOff;
AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
mPhone = phone;
mIsEmergency = isEmergency;
mIsDummy = isDummy;
mAccount = registerPstnPhoneAccount(isEmergency, isDummy);
Log.i(this, "Registered phoneAccount: %s with handle: %s",
mAccount, mAccount.getAccountHandle());
mIncomingCallNotifier = new PstnIncomingCallNotifier((Phone) mPhone);
mPhoneCapabilitiesNotifier = new PstnPhoneCapabilitiesNotifier((Phone) mPhone,
this);
}
}
......
private static TelecomAccountRegistry sInstance;
private final Context mContext;
private final TelecomManager mTelecomManager;
private final TelephonyManager mTelephonyManager;
private final SubscriptionManager mSubscriptionManager;
private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
private Object mAccountsLock = new Object();
private int mServiceState = ServiceState.STATE_POWER_OFF;
private boolean mIsPrimaryUser = true;
// TODO: Remove back-pointer from app singleton to Service, since this is not a preferred
// pattern; redesign. This was added to fix a late release bug.
private TelephonyConnectionService mTelephonyConnectionService;
TelecomAccountRegistry(Context context) {
mContext = context;
mTelecomManager = TelecomManager.from(context);
mTelephonyManager = TelephonyManager.from(context);
mSubscriptionManager = SubscriptionManager.from(context);
}
......
boolean hasAccountEntryForPhoneAccount(PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
if (entry.getPhoneAccountHandle().equals(handle)) {
return true;
}
}
}
return false;
}
private void cleanupPhoneAccounts() {
ComponentName telephonyComponentName =
new ComponentName(mContext, TelephonyConnectionService.class);
// This config indicates whether the emergency account was flagged as emergency calls only
// in which case we need to consider all phone accounts, not just the call capable ones.
// 查看了源代码,该值为false
final boolean emergencyCallsOnlyEmergencyAccount = mContext.getResources().getBoolean(
R.bool.config_emergency_account_emergency_calls_only);
// 获取所有具有CALL_PROVIDER的Capability的PhoneAccountHandle对象
List<PhoneAccountHandle> accountHandles = emergencyCallsOnlyEmergencyAccount
? mTelecomManager.getAllPhoneAccountHandles()
: mTelecomManager.getCallCapablePhoneAccounts(true /* includeDisabled */);
// 遍历获取到的PhoneAccountHandle
for (PhoneAccountHandle handle : accountHandles) {
// 如果该PhoneAccountHandle的ComponentName与Telephony的ComponentName相同,且该PhoneAccountHandle所指向的PhoneAccount未在mAccounts列表当中
if (telephonyComponentName.equals(handle.getComponentName()) &&
!hasAccountEntryForPhoneAccount(handle)) {
Log.i(this, "Unregistering phone account %s.", handle);
// 去注册 该PhoneAccount
mTelecomManager.unregisterPhoneAccount(handle);
}
}
}
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) {
// 遍历所有的Phone对象
for (Phone phone : phones) {
// 获取Phone对象的subId
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.
// 如果subId >= 0,即插入了Sim卡,且可以获取到卡的IccId
if (subscriptionId >= 0 && phone.getFullIccSerialNumber() != null) {
// 则新建一个AccountEntry对象,并添加到mAccounts列表中
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.
// 如果遍历完所有的Phone对象后,mAccounts依旧为空,则此时手机未插入Sim卡
if (mAccounts.isEmpty()) {
// 系统会为手机创建一个用来拨打紧急呼叫的PhoneAccount
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
// 清理掉无用的PhoneAccount
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);
}
}
}
}
......
}
我们来梳理下这个过程,首先看setupAccounts()方法,在这个方法中做了三个关键的步骤:
1. 遍历所有的Phone对象(双卡手机就有两个Phone对象),并获取Phone对象的subId以及IccId。如果subId >= 0,且可以成功获取到IccId,则说明,当前Phone对象所对应的卡槽中插入了SIm卡,此时,创建一个AccountEntry对象,并将其添加到mAccounts列表中
2. 当遍历完所有Phone对象后,查看mAccounts列表是否为空。如果为空,则说明当前手机未插卡,由于手机在未插卡时,仍然需要可以拨打紧急呼叫(110 119 120等等),因此,会为手机创建一个用于拨打紧急呼叫的AccountEntry对象;而如果mAccounts不为空,说明当前手机插入了Sim卡,并且已经创建了相应的AccountEntry对象,直接进行下一步
3. 调用cleanupPhoneAccounts()方法清理无用的PhoneAccount。获取一个包含所有具有CALL_PROVIDER的Capability的PhoneAccountHandle对象的列表(但该列表不包含那些具有EMERGENCY_CALLS_ONLY的Capability的PhoneAccountHandle对象,因为这些对象不是由Sim卡创建而来的),遍历这个列表,将其中那些ComponentName与Telephony的ComponentName相同,但却不在mAccounts列表当中的PhoneAccountHandle所指向的PhoneAccount去注册。
这里有一点儿绕,我们来详细解释一下。首先,如果一个PhoneAccountHandle的ComponentName与Telephony的ComponentName相同,则说明,它是由Telephony模块创建的;其次,如果该PhoneAccountHandle所指向的PhoneAccount对象,并未在mAccounts列表中,则说明,这个PhoneAccount所对应的Sim卡,当前没有插在手机中(因为我们的mAccounts列表中保存的是由当前所有插在手机中的Sim卡所创建的AccountEntry对象),因此该PhoneAccount是没有用的,我们就需要将其去注册。
好了,说了这么多,应该解释的很清楚了。不过还有一点儿,需要说明的。那就是,AccountEntry和PhoneAccount之间到底有什么联系。为什么前面说了这么多,都在说AccountEntry,而不是PhoneAccount呢?
我们来看看上面代码中AccountEntry的构造方法,发现了吗?AccountEntry中有一个变量叫做mAccount,它的值是调用registerPstnPhoneAccount()方法获取到的,而这个方法的返回值就是PhoneAccount类型的。
看到了registerPstnPhoneAccount()方法里面创建PhoneAccount的过程,有没有想起我们在前面提到过的PhoneAccount的内部类Builder,我们的PhoneAccount就是这么通过Builder类来创建的。在创建完PhoneAccount对象之后,还会调用TelecomManager中的registerPhoneAccount()方法(最终是调用了PhoneAccountRegistrar类中的registerPhoneAccount()方法),将其添加到PhoneAccountRegistrar类的mState变量中,并写入xml文件里。
/packages/services/Telephony/src/com/android/services/telephony/TelecomAccountRegistry.java
private PhoneAccount registerPstnPhoneAccount(boolean isEmergency, boolean isDummyAccount) {
......
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(extras)
.setGroupId(groupId)
.build();
// Register with Telecom and put into the account entry.
mTelecomManager.registerPhoneAccount(account);
return account;
}
最后,我们回到PhoneAccountRegistrar类中,看看registerPhoneAccount()方法到底做了什么。
代码首先权限判断,而后调用addOrReplacePhoneAccount()方法,将PhoneAccount添加到mState中,并写入xml文件。而后,通知所有的Listener,PhoneAccount发生了变化。
/packages/services/Telecomm/src/com/android/server/telecom/PhoneAccountRegistrar.java
public void registerPhoneAccount(PhoneAccount account) {
// Enforce the requirement that a connection service for a phone account has the correct
// permission.
if (!phoneAccountRequiresBindPermission(account.getAccountHandle())) {
Log.w(this,
"Phone account %s does not have BIND_TELECOM_CONNECTION_SERVICE permission.",
account.getAccountHandle());
throw new SecurityException("PhoneAccount connection service requires "
+ "BIND_TELECOM_CONNECTION_SERVICE permission.");
}
addOrReplacePhoneAccount(account);
}
private void addOrReplacePhoneAccount(PhoneAccount account) {
Log.d(this, "addOrReplacePhoneAccount(%s -> %s)",
account.getAccountHandle(), account);
// Start _enabled_ property as false.
// !!! IMPORTANT !!! It is important that we do not read the enabled state that the
// source app provides or else an third party app could enable itself.
boolean isEnabled = false;
boolean isNewAccount;
// 通过registerPstnPhoneAccount()方法中创建的PhoneAccount的PhoneAccountHandle到mState中查找
// 判断该PhoneAccount是否已经存在,并据此设置isNewAccount的值
PhoneAccount oldAccount = getPhoneAccountUnchecked(account.getAccountHandle());
if (oldAccount != null) {
mState.accounts.remove(oldAccount);
isEnabled = oldAccount.isEnabled();
Log.i(this, "Modify account: %s", getAccountDiffString(account, oldAccount));
isNewAccount = false;
} else {
Log.i(this, "New phone account registered: " + account);
isNewAccount = true;
}
// When registering a self-managed PhoneAccount we enforce the rule that the label that the
// app uses is also its phone account label. Also ensure it does not attempt to declare
// itself as a sim acct, call manager or call provider.
// 一般第三方应用创建的PhoneAccount会具有SELF_MANAGED的Capability
if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
// Turn off bits we don't want to be able to set (TelecomServiceImpl protects against
// this but we'll also prevent it from happening here, just to be safe).
int newCapabilities = account.getCapabilities() &
~(PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
// Ensure name is correct.
CharSequence newLabel = mAppLabelProxy.getAppLabel(
account.getAccountHandle().getComponentName().getPackageName());
account = account.toBuilder()
.setLabel(newLabel)
.setCapabilities(newCapabilities)
.build();
}
// 将新创建的PhoneAccount添加到mState中
mState.accounts.add(account);
// Set defaults and replace based on the group Id.
maybeReplaceOldAccount(account);
// Reset enabled state to whatever the value was if the account was already registered,
// or _true_ if this is a SIM-based account. All SIM-based accounts are always enabled,
// as are all self-managed phone accounts.
// 判断是否将该account设置为Enable,SIM-based PhoneAccount应该是alway enabled的
account.setIsEnabled(
isEnabled || account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
|| account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED));
// 写入xml文件
write();
// 通知所有的Listener
fireAccountsChanged();
// 如果该PhoneAccount是新创建的,也需要去通知所有的Listener
if (isNewAccount) {
fireAccountRegistered(account.getAccountHandle());
}
}
至此,PhoneAccount也就创建完毕了。
五、何时创建或更新PhoneAccount
前面说了这么多,PhoneAccount到底是在什么情况下会被创建及更新呢?那么,其实很简单,我们要看的就是TelecomAccountRegistry类中的setupAccounts()方法,到底在哪里被调用。
是不是特别清楚,只要这三种情况发生,就会调用setupAccounts()来创建或更新PhoneAccount信息。
/packages/services/Telephony/src/com/android/services/telephony/TelecomAccountRegistry.java
// 当SubscriptionInfo发生改变时
private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
// Any time the SubscriptionInfo changes...rerun the setup
tearDownAccounts();
setupAccounts();
}
};
// 当切换用户时
private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(this, "User changed, re-registering phone accounts.");
int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
UserHandle currentUserHandle = new UserHandle(userHandleId);
mIsPrimaryUser = UserManager.get(mContext).getPrimaryUser().getUserHandle()
.equals(currentUserHandle);
// Any time the user changes, re-register the accounts.
tearDownAccounts();
setupAccounts();
}
};
// 当服务状态由其他转变为IN_SERVICE时
private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onServiceStateChanged(ServiceState serviceState) {
int newState = serviceState.getState();
if (newState == ServiceState.STATE_IN_SERVICE && mServiceState != newState) {
tearDownAccounts();
setupAccounts();
}
mServiceState = newState;
}
};
六、xml文件概览
前面一直提到一个叫做"phone-account-registrar-state"的xml文件,这个文件到底长什么样子呢?下面我们就来看看。
想要获取这个文件,需要一步root过的手机,普通的user版手机是没办法获取到的。在手机root后,可以通过adb命令"adb pull /data/user_de/0/com.android.server.telecom/files/phone-account-registrar-state.xml ./"来将该文件提取当当前目录下。
我们先来看看手机第一次启动后,还未插入过Sim卡时的样子
<phone_account_registrar_state version="9">
<!-- 没有defaultPhoneAccountHandle -->
<default_outgoing />
<accounts>
<phone_account>
<!-- PhoneAccount的PhoneAccountHandle信息 -->
<account_handle>
<phone_account_handle>
<component_name>com.android.phone/com.android.services.telephony.TelephonyConnectionService</component_name>
<!-- 用来拨打紧急呼叫的PhoneAccountHandle的id比较特殊是字母'E' -->
<id>E</id>
<user_serial_number>0</user_serial_number>
<phone_type>1</phone_type>
</phone_account_handle>
</account_handle>
<!-- 这个标签应该对应的是mAddress变量 -->
<handle>tel:</handle>
<!-- 这个标签应该对应的是mSubscriptionAddress变量 -->
<subscription_number>tel:</subscription_number>
<!-- 前面详解介绍过了,1078转换成十六进制436后所代表的Capability的含义 -->
<capabilities>1078</capabilities> <icon>AAAAAQGJUE5HDQoaCgAAAA1JSERSAAAAMAAAADwIBgAAACDAOfwAAAAEc0JJVAgICAh8CGSIAAAC UElEQVRoge2aT2sTQRyG3/eXNrttukQFb4J4DKX9DHsq5NDc5iptQYqItf9sL1Z6UMHvIHjRXvIJ FNG7HiwEoX4Az54j3X09JIr/itmkzWRhnvPu5HkyO8yyDDEizWYzSpJkXdJTAPGo452FpC7JjqRn JNvtdvsrAHGUQZ1zcyTXLlr+D04BvDazg0ajcTx0gHOuTnInz/M9ktE5Cg6CALypVCorlWHuds7V Je0A2CBZO1+3gSCA65K+FA7oPzabkjZJ1vuD+cAAXC304865GUnrAB6TnL0Yr0J0bdAr0zSNzewm yUcTIg8A0UAz0Gq1kmq1eo/kPoC5C5YqxNT/Lmi1WkkURfsA7mLC5IHeQjiT5eXl2Wq1ui3pDoBk TE6FOHMG0jSN4zheA7AFoD4+pWL8cw30Xw9WJT0BcHnMToX4awaWlpZqSZJsSdrFBP/zP/gtwDk3 Q/J+X97HDluYn4s4TdMYwLakDZREHujPwC877C6AS36VimHNZjMys1UAD1AyeQCYqtVqt/M8PyB5 xbfMMBiAHQCllAcAIzmRO+ygDPw2OqmEAN+EAN+EAN+UP0BSqSPM05e1c8NQ8seo1PJACPBPCPBN CPBNCPBNCPBNCPBNCPBNCPBNCPBNCPBNCPCNSer6lhiBrpH85NtiBE4MwHP0jjKWjYzkkU1PT78E 8Aq9o4ylQJIAvDWzF5VOp9NdWFg4kXSN5A0AQx3FHBeSTs3sXZZlD+fn5z8bADUajeMsy25lWbZF 8gOASVzY3yR9NLM9SSuLi4vvDw8P8+9H1Z9NknqPGwAAAABJRU5ErkJggg== </icon>
<highlight_color>0</highlight_color>
<label>紧急呼叫</label>
<short_description>只能拨打紧急呼叫电话</short_description>
<supported_uri_schemes length="2">
<value>tel</value>
<value>voicemail</value>
</supported_uri_schemes>
<extras>
<value key="android.telecom.extra.SUPPORTS_VIDEO_CALLING_FALLBACK" type="boolean">false</value>
</extras>
<enabled>true</enabled>
<supported_audio_routes>15</supported_audio_routes>
</phone_account>
</accounts>
</phone_account_registrar_state>
下面来看看插入两张Sim卡时(卡一是电信卡,卡二是联通卡),该文件会变化成什么样子:
<phone_account_registrar_state version="9">
<!-- 根据IccId可以看出,defaultPhoneAccount为卡二,我们可以在Settings中更改这个设置 -->
<default_outgoing>
<default_outgoing_phone_account_handle>
<user_serial_number>0</user_serial_number>
<group_id></group_id>
<account_handle>
<phone_account_handle>
<component_name>com.android.phone/com.android.services.telephony.TelephonyConnectionService</component_name>
<id>898601XXXXXXXXXXXXXX</id>
<user_serial_number>0</user_serial_number>
</phone_account_handle>
</account_handle>
</default_outgoing_phone_account_handle>
</default_outgoing>
<!-- 手机插了两张卡,一卡是电信卡,二卡是联通卡 -->
<accounts>
<!-- 卡一的PhoneAccount -->
<phone_account>
<!-- 该PhoneAccount的PhoneAccountHandle信息 -->
<account_handle>
<phone_account_handle>
<component_name>com.android.phone/com.android.services.telephony.TelephonyConnectionService</component_name>
<!-- 由于会暴露隐私,所以这里我把IccIc的后面14位隐去了 -->
<id>898603XXXXXXXXXXXXXX</id>
<!-- 用户Id为0 -->
<user_serial_number>0</user_serial_number>
</phone_account_handle>
</account_handle>
<!-- 由于获取不到卡一的MDN,所以这里显示不出来 -->
<handle>tel:</handle>
<!-- 由于获取不到卡一的MDN,所以这里显示不出来 -->
<subscription_number>tel:</subscription_number>
<!-- 前面详解介绍过了,1078转换成十六进制436后所代表的Capability的含义 -->
<capabilities>1078</capabilities>
<icon>AAAAAQGJUE5HDQoaCgAAAA1JSERSAAAAPAAAADwIBgAAADr82XIAAAAEc0JJVAgICAh8CGSIAAAC 60lEQVRoge2bP2gTURzHv7/Ln0uaptqmFhRb0AasTStCxYqjoIN7N+kk2lriH1CrHUq7uAiCFNQi uOjq6KKDYxHqYmgcGurSoZNVCtbcJfk5XM4GxeZd7t0lXt8Hbgr33u9z792X5O4XQKFQ/M+QH5Ok s6u6zPH0DYNXMoMlzFEFYAKIRc/1UJgpM5aPxDppGoQZAMJFCWAQaImZF81t892nlyd+iEp7JMw0 cmWtg/FziojuA0hInsuW2yTCo1AksvBhIb0lIq1JLMKuhYYnc/ursrcAtMmf4zf7mHGzbBQvWLJc 96JKFra2sc7ha0TaDQBdsFZW9k6imqMLoHHRe1misDVhPEV3mHEP4G54H4oEgBjaaGYsHxE5ISxn Xjug8tPMmP+jIC+pjs8dBxJtQosnQdgOKEyBcBdWoHixjaXgUrgaUOXQZE1Ataws4Ep4J6CY6DrA XgWUVBoMraYElBQaWOGmBZQUHK6wFVB6J27XBBTgUvbyxW4sPzuO1/P9boYRwsEKexdQQ0fjbocQ RlDYu4Dq64ni9EDC7TDCCG5pbwKqPaZhdvwgomH/bn+hFU5nV3U2zFkA9tc3VxWe7I/j7FA7zo90 oLcn6mYoxzhJ6RIsYceyx3pjeDhxGPGohriuIRZtXqA7EW74no1GCIdSQt/tPUfSj4fdya1t49TE 539+/mrmCAb6Yn6U4sUDgNZGCQcdJRx0lHDQUcJBRwkHHSUcdJRw0FHCQUcJB509J+zLQ7x6XHrw xbe59twKK+Ggo4SDjpCwvmEwAAPWG3+ZTaJusesx1pMlobqEhFcygyUCLaFlhWm5kEqbIicICDNh jioV4AmAzeokFTRXnGuO70T8fKd3encEhK0u1WKo8h6MxyB8Ret07GyBeDFi8lvR5lIHhTONZgvJ UtE8R0RXGXwGgD/vOP/GAPgjAy9Mrfwm93T4m0cN4vZVtJpcRBs6ZbOeLHEhlTYb+QtAA9S/T/yj lWpRKJrCL8Wo6rkfWXq1AAAAAElFTkSuQmCC </icon>
<highlight_color>-13408298</highlight_color>
<label>中国电信</label>
<short_description>SIM 卡,插槽:0</short_description>
<!-- 该PhoneAccount所能支持的uriScheme -->
<supported_uri_schemes length="2">
<value>tel</value>
<value>voicemail</value>
</supported_uri_schemes>
<!-- Extra信息 -->
<extras>
<value key="android.telecom.extra.SUPPORTS_VIDEO_CALLING_FALLBACK" type="boolean">false</value>
<value key="android.telecom.extra.SORT_ORDER" type="string">0</value>
</extras>
<!-- 该PhoneAccount是enabled状态 -->
<enabled>true</enabled>
<supported_audio_routes>15</supported_audio_routes>
</phone_account>
<!-- 卡二的PhoneAccount -->
<phone_account>
<!-- 该PhoneAccount的PhoneAccountHandle信息 -->
<account_handle>
<phone_account_handle>
<component_name>com.android.phone/com.android.services.telephony.TelephonyConnectionService</component_name>
<!-- 由于会暴露隐私,所以这里我把IccIc的后面14位隐去了 -->
<id>898601XXXXXXXXXXXXXX</id>
<!-- 用户Id为0 -->
<user_serial_number>0</user_serial_number>
</phone_account_handle>
</account_handle>
<!-- 由于会暴露隐私,所以这里将手机号后8位隐去了 -->
<handle>tel:%2B86156XXXXXXXX</handle>
<!-- 由于会暴露隐私,所以这里将手机号后8位隐去了 -->
<subscription_number>tel:%2B86156XXXXXXXX</subscription_number>
<capabilities>1078</capabilities>
<icon>AAAAAQGJUE5HDQoaCgAAAA1JSERSAAAAPAAAADwIBgAAADr82XIAAAAEc0JJVAgICAh8CGSIAAAF aklEQVRoge2bWWxUVRiAv3uZma7T0g6lpaAplsYU2lhqsIAgBIUYVmPL5gIYEhBxWIxsPo2JEWNw IRi0YRVZqwYFUUHjltBG2oJYDG6JUmnrQKFMN6edMteHOwOIYM+d3jMdSb9kMi/3nP//7jnnv8uc gR566OH/jBKWKE5nlKn9ORwa0IHL5Ud30ESbyhRWcLmseM+tQlGeM5KUAO1AGSjF+KI/Y926VtH+ ZQkrrFqQgGpdDKwB4kyOFZRrQFNeJVrZgGtDEwLSqolJBFFYvag3qnUxCsuBWAkxgiSiastouzwB XbbTk2q2sD6N6fUUsBSN5EASZs8k5cpHIxlNmYPgWjZTWA/orV+Boq0G+iC/KOrSCgX6ie4ci2mB rxQo7fnrEpJJsP8E+F1o8MwQ1gtU2/nFKMpKrq6l8FzyDNJVYb1AKeoiFJajEUsEy0LX1nC4CpSp hDrC1xeo4MhGPKEId1eBMgWjwqYUKKuq0j+hNzFWK2cuXaTV5zOYRugYEe5SgRrcNw3niDGMGZhF pqMPtl56aE3TqPY0UHG2ms3lR/n0l9PGLQwgOjKBaVy/AkVbwtWbCqH2L4yfzLJ7xxFns3V67IHT VTgPlFDtaRBMDQAvUS1JuLZ7OztQdIRDLlBvTpvJkwWjAfB4/2LPyQoOnK7iB3cdSTGxDE0fwKiM TB7NG0a0xcrU7FwykpKZsPUN3M1NomGEEUvc6Ywi3t8IBG/fhNrNzS9ge9HjAPx03s303Vuoctfe 8Ni8fv3ZOG0WI24fCMDe7yuZvXebUHoYGGEj1+GOwLeQrFVVcd0/EYDGNi9FuzffVBbgu7oapux4 i5/rzwEwPWcod6ffZiA9MYwIG6rG03PzyUhyALClvJRT7rpO21xobWHtV4cB6KWqFObkGUhPDBnP wwBMyMoG9Cq8tbJMuF1J1XGa29sAuKvfANPzkiY8uG8aAO7mJqHRDdLq8/FnUyMAjtg40/OSJpwS Fw/o09QoFlVP67Lfb2pOIFE4xqpfc+tbmw21s9ui6BtvB+Bci/mXJbNeAPyLtBfXhNSuMCeP2MDJ Kj3zm5kpARJHOBSiLRaeHf0AAPUtzew+WW56jIgSLn5oNkNS+wHw8jefU9PoMT1GRAhHWyxsK3yM OfkFAFTWVPP60S+kxJK2hkXpn5DIzhnzGHtHFgCn3LUU7dqMT0KFhm4WnpGbz2uTCklPSATgRO0f TN1RzNnGS9JidotwUnQMr0x6mLn5w1EVBU3TePv4tyz76D08bZ3e/3eJsAtPvHMI6ycXMciRAkBt o4dnDr3PvqrjYYkfNmG7LYq1D05j4T2jsKgqHX4/O08cY/XhD6U8996MsAhnp6Syb/Z8ctPSAf3Z eMnBdzny64/hCP8PpAvflzGIPbOeID0hEb+msaWilBUf75e+Vm+GVOHslFRKHplParydto4OnAdL 2FReKjNkp0gTttui2DVzHqnxdrwdPhbs38M7J47JCieMtDutp0eMYWjgFc3KTz6ICFmQJGxVVeYP GwlARU01G8q+lhEmJKRM6fFZ2WQm9wGgrtHD0pFjQ+qn1ddu+pqXIpwTeOIBmJKdy5Ts3JD6cTc3 mS4sZUpnJqfI6NYUjLyIbwBi5KYTMlJexN8S9Ajf6vQI3xB992o7+i/+Zm4S7SrBfNq5YBfKS3SE O4AyIle4AodDaN+EiLCCy+VH1TYCDYEAfrpXXLvm40Fh0zV7p/8TEWF944qVL9G09cBFkY7DRBMo xdhsRxDcXGokcQWX006bNg60hcBwIDrERLtKO1CJomzFf/kQL715CUkbxINnMbALT2xDp+lcsGs4 HL5Q/gIQCpEynSGycumhh+7gbze1tkGq7VlLAAAAAElFTkSuQmCC </icon>
<highlight_color>-16746133</highlight_color>
<label>CARD 2</label>
<short_description>SIM 卡,插槽:1</short_description>
<!-- 该PhoneAccount所能支持的uriScheme -->
<supported_uri_schemes length="2">
<value>tel</value>
<value>voicemail</value>
</supported_uri_schemes>
<!-- Extra信息 -->
<extras>
<value key="android.telecom.extra.SUPPORTS_VIDEO_CALLING_FALLBACK" type="boolean">false</value>
<value key="android.telecom.extra.SORT_ORDER" type="string">1</value>
</extras>
<!-- 该PhoneAccount是enabled状态 -->
<enabled>true</enabled>
<supported_audio_routes>15</supported_audio_routes>
</phone_account>
</accounts>
</phone_account_registrar_state>
好啦,以上就是我在学习PhoneAccount的过程中记录的全部知识,希望能对想了解PhoneAccount的同学们有所帮助。如果文章中有任何错误,也欢迎大家批评指正,我一定第一时间进行修改。