轉載請註明出處: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的同學們有所幫助。如果文章中有任何錯誤,也歡迎大家批評指正,我一定第一時間進行修改。