Android 8.0 PhoneAccount全面解析

轉載請註明出處: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&#10;UElEQVRoge2aT2sTQRyG3/eXNrttukQFb4J4DKX9DHsq5NDc5iptQYqItf9sL1Z6UMHvIHjRXvIJ&#10;FNG7HiwEoX4Az54j3X09JIr/itmkzWRhnvPu5HkyO8yyDDEizWYzSpJkXdJTAPGo452FpC7JjqRn&#10;JNvtdvsrAHGUQZ1zcyTXLlr+D04BvDazg0ajcTx0gHOuTnInz/M9ktE5Cg6CALypVCorlWHuds7V&#10;Je0A2CBZO1+3gSCA65K+FA7oPzabkjZJ1vuD+cAAXC304865GUnrAB6TnL0Yr0J0bdAr0zSNzewm&#10;yUcTIg8A0UAz0Gq1kmq1eo/kPoC5C5YqxNT/Lmi1WkkURfsA7mLC5IHeQjiT5eXl2Wq1ui3pDoBk&#10;TE6FOHMG0jSN4zheA7AFoD4+pWL8cw30Xw9WJT0BcHnMToX4awaWlpZqSZJsSdrFBP/zP/gtwDk3&#10;Q/J+X97HDluYn4s4TdMYwLakDZREHujPwC877C6AS36VimHNZjMys1UAD1AyeQCYqtVqt/M8PyB5&#10;xbfMMBiAHQCllAcAIzmRO+ygDPw2OqmEAN+EAN+EAN+UP0BSqSPM05e1c8NQ8seo1PJACPBPCPBN&#10;CPBNCPBNCPBNCPBNCPBNCPBNCPBNCPBNCPCNSer6lhiBrpH85NtiBE4MwHP0jjKWjYzkkU1PT78E&#10;8Aq9o4ylQJIAvDWzF5VOp9NdWFg4kXSN5A0AQx3FHBeSTs3sXZZlD+fn5z8bADUajeMsy25lWbZF&#10;8gOASVzY3yR9NLM9SSuLi4vvDw8P8+9H1Z9NknqPGwAAAABJRU5ErkJggg==&#10;</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&#10;60lEQVRoge2bP2gTURzHv7/Ln0uaptqmFhRb0AasTStCxYqjoIN7N+kk2lriH1CrHUq7uAiCFNQi&#10;uOjq6KKDYxHqYmgcGurSoZNVCtbcJfk5XM4GxeZd7t0lXt8Hbgr33u9z792X5O4XQKFQ/M+QH5Ok&#10;s6u6zPH0DYNXMoMlzFEFYAKIRc/1UJgpM5aPxDppGoQZAMJFCWAQaImZF81t892nlyd+iEp7JMw0&#10;cmWtg/FziojuA0hInsuW2yTCo1AksvBhIb0lIq1JLMKuhYYnc/ursrcAtMmf4zf7mHGzbBQvWLJc&#10;96JKFra2sc7ha0TaDQBdsFZW9k6imqMLoHHRe1misDVhPEV3mHEP4G54H4oEgBjaaGYsHxE5ISxn&#10;Xjug8tPMmP+jIC+pjs8dBxJtQosnQdgOKEyBcBdWoHixjaXgUrgaUOXQZE1Ataws4Ep4J6CY6DrA&#10;XgWUVBoMraYElBQaWOGmBZQUHK6wFVB6J27XBBTgUvbyxW4sPzuO1/P9boYRwsEKexdQQ0fjbocQ&#10;RlDYu4Dq64ni9EDC7TDCCG5pbwKqPaZhdvwgomH/bn+hFU5nV3U2zFkA9tc3VxWe7I/j7FA7zo90&#10;oLcn6mYoxzhJ6RIsYceyx3pjeDhxGPGohriuIRZtXqA7EW74no1GCIdSQt/tPUfSj4fdya1t49TE&#10;539+/mrmCAb6Yn6U4sUDgNZGCQcdJRx0lHDQUcJBRwkHHSUcdJRw0FHCQUcJB509J+zLQ7x6XHrw&#10;xbe59twKK+Ggo4SDjpCwvmEwAAPWG3+ZTaJusesx1pMlobqEhFcygyUCLaFlhWm5kEqbIicICDNh&#10;jioV4AmAzeokFTRXnGuO70T8fKd3encEhK0u1WKo8h6MxyB8Ret07GyBeDFi8lvR5lIHhTONZgvJ&#10;UtE8R0RXGXwGgD/vOP/GAPgjAy9Mrfwm93T4m0cN4vZVtJpcRBs6ZbOeLHEhlTYb+QtAA9S/T/yj&#10;lWpRKJrCL8Wo6rkfWXq1AAAAAElFTkSuQmCC&#10;</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>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&#10;</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的同學們有所幫助。如果文章中有任何錯誤,也歡迎大家批評指正,我一定第一時間進行修改。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章