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>
          <!-- 由于会暴露隐私,所以这里我把IccId的后面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>
          <!-- 由于会暴露隐私,所以这里我把IccId的后面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的同学们有所帮助。如果文章中有任何错误,也欢迎大家批评指正,我一定第一时间进行修改。

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页