Telephony--PhoneAccount

对PhoneAccount的使用体会并不深,所以很多知识点漏了或者写的不清晰,后续get到新体会时再完善吧。内容显的杂乱,简单分了五部分:
1. PhoneAccount的作用
2. PhoneAccount包含的信息
3. PhoneAccount相关的属性
4. SIMs based phone accounts
5. 题外篇ConnectionService


1. PhoneAccount的作用

作为一名Android系统Telephony研发人员,我清楚拨打,接听电话都需要PhoneAccount。但是心里对PhoneAccount总是感到一些疑惑,因为平时接触到的PhoneAccount都是基于SIM的或者Sip的; 所以对PhoneAccount的理解一直局限在拨打/接听电话时,使用哪张SIM,哪个Sip帐号; 没能从Android 设计层面上一直注重的扩展性方面去理解PhoneAccount。主要还是自己Level不够; 另外可能也是一直系统研发,缺少第三方App研发的思维。
扯远了,赶紧回来!
从设计层面来理解,PhoneAccount有就像桥梁,起到了连接的作用。为什么呢?
Telecom内有call,audio routing等功能,还负责bind in-call UI, 所以对于想使用这些资源的App来说都应该构建一个PhoneAccount对象,并使用TelecomManager注册到Telecom中。因为PhoneAccount对象中有ConnectionService信息,Telecom通过ConnectionService就可以和App(类似TeleSerfvice.apk)通信。例如 TeleService.apk在启动时就通过调用TelecomAccountRegistry创建PhoneAccount,这些PhoneAccount对象内包含了TeleService.apk的包名”com.android.phone”,以及实现了ConnectionService的TelephonyConnectionService。

Telecom.apk和framework- telecom完成了PhoneAccount信息的注册,更新和提供接口的任务。

下面是源码PhoneAccount.java里面的注释虽然简单,但是很明了。

/**
 * Represents a distinct method to place or receive a phone call. Apps which can place calls
 * and want those calls to be integrated into the dialer and in-call UI should build an 
 * instance of this class and register it with the system using {@link TelecomManager}.
 * <p>
 * {@link TelecomManager} uses registered {@link PhoneAccount}s to present the user with
 * alternative options when placing a phone call. When building a {@link PhoneAccount}, the app
 * should supply a valid {@link PhoneAccountHandle} that references the connection service
 * implementation Telecom will use to interact with the app.
 */

2. PhoneAccount包含的信息

每一个PhoneAccount创建时必须有一个PhoneAccountHandle作为唯一标识; 每个PhoneAccountHandle只包含两部分信息:

The unique identifier for a PhoneAccount. A **PhoneAccountHandle** is made of two parts:

The component name of the associated connection service.
A string identifier that is unique across PhoneAccountHandles with the same component name.

一部分是包含ConnectionService信息的component;
[例如]

private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT =
            new ComponentName("com.android.phone",
                    "com.android.services.telephony.TelephonyConnectionService");

另外一部分是唯一标识,component不是唯一的,所以需要一个唯一标识。
[例如]
SIM based的PhoneAccount使用ICC卡的ID。

下面简单解释了PhoneAccount中包含的信息,其中一些,我们可以在手机setting的SIM cards或者Status菜单中看到。

    //唯一标识Id
    private final PhoneAccountHandle mAccountHandle;
    //看了getAddress的注释,没看明白,一头雾水; 不过这个应该是显示用的,当我们使用这个PhoneAccount
    //拨打电话/来电时,这个信息可以用来显示。所以我们看到这个值可以用TelephonyManager.getLine1Number(int)
    //来赋值,这个方法获取的值是通过TelephonyManager.setLine1NumberForDisplay设置的。
    private final Uri mAddress;
    //对于GSM,这个值是SIM卡里获取的MSISDN;对于CDMA,这个值是MDN。
    private final Uri mSubscriptionAddress;
    //用来标识当前PhoneAccount的使用性,比如是否可以拨打紧急电话(CAPABILITY_PLACE_EMERGENCY_CALLS)
    private final int mCapabilities;
    //颜色值,显示的时候会用到。
    private final int mHighlightColor;
    //这个不好解释,label就是label,存储一些容易辨识的信息 比如China Mobile等这类运营商的名称。
    private final CharSequence mLabel;
    //描述信息,比如用于Emergency call等。
    private final CharSequence mShortDescription;
    //支持的Uri,比如SIP, Tele, VoiceMail
    private final List<String> mSupportedUriSchemes;
    //支持的Audio通路, 比如扬声器,听筒,蓝牙等。
    private final int mSupportedAudioRoutes;
    //对应的图片
    private final Icon mIcon;
    //用来存储其他信息
    private final Bundle mExtras;
    //当前PhoneAccount是否可用
    private boolean mIsEnabled;
    //group Id是向Telecom中注册新PhoneAccount时用的。当注册新的PhoneAccount时,如果新PhoneAccount和旧
    //PhoneAccount的group Id相同,并且connectionservice相同,那么新PhoneAccount会替代旧的PhoneAccount
    //包括旧PhoneAccount的enable status。前提是就PhoneAccount是default PhoneAccount.
    //可以参考PhoneAccountRegistrar.maybeReplaceOldAccount(PhoneAccount newAccount)方法。
    private String mGroupId;

3. PhoneAccount相关的属性

PhoneAccount的属性有十几个, 但是下面三个属性不是很好理解。
CAPABILITY_CONNECTION_MANAGER:
和拥有这一属性的phoneAccount绑定的connectionService可以管理phone calls,包括使用其特有实现(比如VoIP call)去构造call对象,而非使用telephony stack。如果用户选择使用该PhoneAccount作为默认connection manager, 那么当用户选择使用传统的SIM-based telephony stack拨打电话时, 和这个PhoneAccount关联的ConnectionService会被优先使用。
可以管理SIM-based的call。
Flag indicating that this PhoneAccount can act as a connection manager for other connections. The ConnectionService associated with this PhoneAccount will be allowed to manage phone calls including using its own proprietary phone-call implementation (like VoIP calling) to make calls instead of the telephony stack.

When a user opts to place a call using the SIM-based telephony stack, the ConnectionService associated with this PhoneAccount will be attempted first if the user has explicitly selected it to be used as the default connection manager.

CAPABILITY_CALL_PROVIDER:
这个flag表示PhoneAccount可以构造phone call来代替传统的SIM-based telephony call。
当使用传统的SIM-based telephony stack拨打电话时,拥有这个flag的PhoneAccount都可以作为一种直观(不同的)方式。
该标志不同于CAPABILITY CONNECTION MANAGER,因为后者不允许管理或拨打来自built-in telephony stack 的call(这里的built-in telephony stack指什么?肯定不是SIM-based telephony stack吧,难道是第三放App内的独有实现?)。
Flag indicating that this PhoneAccount can make phone calls in place of traditional SIM-based telephony calls. This account will be treated as a distinct method for placing calls alongside the traditional SIM-based telephony stack. This flag is distinct from CAPABILITY_CONNECTION_MANAGER in that it is not allowed to manage or place calls from the built-in telephony stack.

CAPABILITY_SELF_MANAGED:
这个flag用于表示PhoneAccount会管理自己的Connections。这个flag适用于那些想使用Telecom framekwrok的call和audio routing功能,但又不想使用默认phone app(TeleService.apk) connection功能的独立应用。
self-managed ConnectionService需要为自己创建的Connections提供GUI。
/**
* Flag indicating that this {@link PhoneAccount} is responsible for managing its own
* {@link Connection}s. This type of {@link PhoneAccount} is ideal for use with standalone
* calling apps which do not wish to use the default phone app for {@link Connection} UX,
* but which want to leverage the call and audio routing capabilities of the Telecom framework.
*


* When set, {@link Connection}s created by the self-managed {@link ConnectionService} will not
* be surfaced to implementations of the {@link InCallService} API. Thus it is the
* responsibility of a self-managed {@link ConnectionService} to provide a user interface for
* its {@link Connection}s.
*


* Self-managed {@link Connection}s will, however, be displayed on connected Bluetooth devices.

Telecom/testapps这个例子展示了我们该如何使用self-manged connectionservice; 需要的权限,注册PhoneAccount,placeCall, 创建self-manged connectionservice等。

self-managed connectionservice是如何启动in-call UI的呢?
针对incoming call,android.telecom.Connection添加了onShowIncomingCallUi方法,在ConnectionService.createConnection方法中会调用onShowIncomingCallUi方法,这个方法内可以发生notification或者启动in-call UI.
针对out-going call仍然需求去bind 相应service, 不过只有包含了下面meta-data的service才可以被bind。

<meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS" android:value="true" />

注意
CAPABILITY_SELF_MANAGED 是不能和CAPABILITY_CALL_PROVIDER
CAPABILITY_CONNECTION_MANAGER以及CAPABILITY_SIM_SUBSCRIPTION同时使用的。
A self-managed android.telecom.PhoneAccount cannot also be a call provider.
A self-managed android.telecom.PhoneAccount cannot also be a SIM subscription.
A self-managed android.telecom.PhoneAccount cannot also be a connection manager.


4. SIMs based phone accounts

4.1 创建

TelecomAccountRegistry.setupOnBoot方法内注册了三个监听,当监听内容发生改变时会将之前的accounts销毁,并重新setup accounts。

  1. 通过SubscriptionManager监听SubscriptionInfo变化。
  2. 通过TelephonyManger监听service state变化。
  3. 通过context监听user switch。
    /**
     * Sets up all the phone accounts for SIMs on first boot.
     */
    void setupOnBoot() {
        // TODO: When this object "finishes" we should unregister by invoking
        // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener);
        // This is not strictly necessary because it will be unregistered if the
        // notification fails but it is good form.

        // Register for SubscriptionInfo list changes which is guaranteed
        // to invoke onSubscriptionsChanged the first time.
        SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
                mOnSubscriptionsChangedListener);

        // We also need to listen for changes to the service state (e.g. emergency -> in service)
        // because this could signal a removal or addition of a SIM in a single SIM phone.
        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);

        // Listen for user switches.  When the user switches, we need to ensure that if the current
        // use is not the primary user we disable video calling.
        mContext.registerReceiver(mUserSwitchedReceiver,
                new IntentFilter(Intent.ACTION_USER_SWITCHED));
    }

TelecomAccountRegistry.tearDownAccounts()方法负责清空之前的PhoneAccount数据,这里的PhoneAccount数据只是TelecomAccountRegistry中LinkedList类型成员变量mAccounts中的数据,而非存储到手机xml中的数据。

    private void tearDownAccounts() {
        synchronized (mAccountsLock) {
            for (AccountEntry entry : mAccounts) {
                entry.teardown();
            }
            mAccounts.clear();
        }
    }

TelecomAccountRegistry.setupAccounts()方法负责创建新的PhoneAccount。setupAccounts()会根据phone 对象的数量(手机SIM卡的数量)来创建PhoneAccount。在创建的过程中,使用到了AccountEntry类,每创建一个PhoneAcount对象都需要创建一个AccountEntry对象。在AccountEntry的构造函数里我们发现每个AccountEntry对象会持有一个PstnIncomingCallNotifier对象。所以PstnIncomingCallNotifier和phone对象是一一对应的。

下面是setupAccounts方法, 这个方法注释很详细,直接拿来贴上。

    private void setupAccounts() {
        // Go through SIM-based phones and register ourselves -- registering an existing 
        // account will cause the existing entry to be replaced.
        Phone[] phones = PhoneFactory.getPhones();
        Log.d(this, "Found %d phones.  Attempting to register.", phones.length);

        final boolean phoneAccountsEnabled = mContext.getResources().getBoolean(
                R.bool.config_pstn_phone_accounts_enabled);

        synchronized (mAccountsLock) {
            if (phoneAccountsEnabled) {
                for (Phone phone : phones) {
                    int subscriptionId = phone.getSubId();
                    Log.d(this, "Phone with subscription id %d", subscriptionId);
                    // setupAccounts can be called multiple times during service changes.
                    //Don't add an account if the Icc has not been set yet.
                    if (subscriptionId >= 0 && phone.getFullIccSerialNumber() != null) {
                        mAccounts.add(new AccountEntry(phone, false /* emergency */,
                                false /* isDummy */));
                    }
                }
            }

            // If we did not list ANY accounts, we need to provide a "default" SIM account
            // for emergency numbers since no actual SIM is needed for dialing emergency
            // numbers but a phone account is.
            if (mAccounts.isEmpty()) {
                mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(), 
                true /* emergency */, false /* isDummy */));
            }

            // Add a fake account entry.
            if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
                mAccounts.add(new AccountEntry(phones[0], false /* emergency */,
                        true /* isDummy */));
            }
        }

        // Clean up any PhoneAccounts that are no longer relevant
        cleanupPhoneAccounts();

        // At some point, the phone account ID was switched from the subId to the iccId.
        // If there is a default account, check if this is the case, and upgrade the 
        // default account from using the subId to iccId if so.
        PhoneAccountHandle defaultPhoneAccount =
                mTelecomManager.getUserSelectedOutgoingPhoneAccount();
        ComponentName telephonyComponentName =
                new ComponentName(mContext, TelephonyConnectionService.class);

        if (defaultPhoneAccount != null &&
                telephonyComponentName.equals(defaultPhoneAccount.getComponentName()) &&
                !hasAccountEntryForPhoneAccount(defaultPhoneAccount)) {

            String phoneAccountId = defaultPhoneAccount.getId();
            if (!TextUtils.isEmpty(phoneAccountId) && TextUtils.isDigitsOnly(phoneAccountId)) {
                PhoneAccountHandle upgradedPhoneAccount =
                        PhoneUtils.makePstnPhoneAccountHandle(
                                PhoneGlobals.getPhone(Integer.parseInt(phoneAccountId)));

                if (hasAccountEntryForPhoneAccount(upgradedPhoneAccount)) {
                    mTelecomManager.setUserSelectedOutgoingPhoneAccount(upgradedPhoneAccount);
                }
            }
        }
    }

AccountEntry.registerPstnPhoneAccount方法做了具体的工作,获取构建PhoneAccount需要的数据,并将新构造的PhoneAccount对象注册到Telecom中。

        /**
         * Registers the specified account with Telecom as a PhoneAccountHandle.
         */
        private PhoneAccount registerPstnPhoneAccount(boolean isEmergency, boolean isDummyAccount) {
            String dummyPrefix = isDummyAccount ? "Dummy " : "";

            // 首先创建一个PhoneAccountHandle对象。
            // Build the Phone account handle.
            PhoneAccountHandle phoneAccountHandle =
                    PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
                            mPhone, dummyPrefix, isEmergency);

            // 下面开始准备创建PhoneAccount对象所需要的数据
            // Populate the phone account data.
            int subId = mPhone.getSubId();
            String subscriberId = mPhone.getSubscriberId();
            int color = PhoneAccount.NO_HIGHLIGHT_COLOR;
            int slotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
            String line1Number = mTelephonyManager.getLine1Number(subId);
            if (line1Number == null) {
                line1Number = "";
            }
            String subNumber = mPhone.getLine1Number();
            if (subNumber == null) {
                subNumber = "";
            }

            String label;
            String description;
            Icon icon = null;

            //至于PhoneAccount中包含的信息,前面已经介绍过了,获取数据部分就省略了。
            ......
            获取数据
            ......
            PhoneAccount account = PhoneAccount.builder(phoneAccountHandle, label)
                    .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, line1Number, null))
                    .setSubscriptionAddress(
                            Uri.fromParts(PhoneAccount.SCHEME_TEL, subNumber, null))
                    .setCapabilities(capabilities)
                    .setIcon(icon)
                    .setHighlightColor(color)
                    .setShortDescription(description)
                    .setSupportedUriSchemes(Arrays.asList(
                            PhoneAccount.SCHEME_TEL, PhoneAccount.SCHEME_VOICEMAIL))
                    .setExtras(instantLetteringExtras)
                    .setGroupId(groupId)
                    .build();

            // 注册PhoneAccount
            // Register with Telecom and put into the account entry.
            mTelecomManager.registerPhoneAccount(account);

            return account;
        }

注册PhoneAccount使用的是TelecomManager.registerPhoneAccount方法,
经过TelecomServiceImpl.mBinderImpl.registerPhoneAccount方法后,最终会调用PhoneAccountRegistrar.registerPhoneAccount方法。这个方法会继续调用其他方法,将新PhoneAccount放进手机的“phone-account-registrar-state.xml”文件内; 这个文件在手机中的路径是”/data/user_de/0/com.android.server.telecom/”。

4.2 PhoneAccount在拨打和来电中的使用

4.2.1 来电
来电时,如果收到来电事件时没有一个可用的PhoneAccount,那么来电会被挂断。
在创建PhoneAccount时,PstnIncomingCallNotifier对象会被创建,该对象会调用phone对象的registerForNewRingingConnection方法注册来电监听,当有来电时PstnIncomingCallNotifier.mHandler会收到EVENT_NEW_RINGING_CONNECTION事件,处理方法是handleNewRingingConnection:

    /**
     * Verifies the incoming call and triggers sending the incoming-call intent to Telecom.
     *
     * @param asyncResult The result object from the new ringing event.
     */
    private void handleNewRingingConnection(AsyncResult asyncResult) {
        Log.d(this, "handleNewRingingConnection");
        Connection connection = (Connection) asyncResult.result;
        if (connection != null) {
            Call call = connection.getCall();

            // Final verification of the ringing state before sending the intent to Telecom.
            if (call != null && call.getState().isRinging()) {
                sendIncomingCallIntent(connection);
            }
        }
    }

sendIncomingCallIntent方法尝试找到一个可用的PhoneAccount,然后将incoming事件传到Telecom;否则会挂断通话。

    /**
     * Sends the incoming call intent to telecom.
     */
    private void sendIncomingCallIntent(Connection connection) {
        Bundle extras = new Bundle();
        if (connection.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED &&
                !TextUtils.isEmpty(connection.getAddress())) {
            Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, connection.getAddress(), null);
            extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri);
        }

        // Specifies the time the call was added. This is used by the dialer for analytics.
        extras.putLong(TelecomManager.EXTRA_CALL_CREATED_TIME_MILLIS,
                SystemClock.elapsedRealtime());

        PhoneAccountHandle handle = findCorrectPhoneAccountHandle();
        if (handle == null) {
            try {
                connection.hangup();
            } catch (CallStateException e) {
                // connection already disconnected. Do nothing
            }
        } else {
            TelecomManager.from(mPhone.getContext()).addNewIncomingCall(handle, extras);
        }
    }

findCorrectPhoneAccountHandle方法负责查找和phone对象关联的PhoneAccount。

4.2.2拨打电话
这部分涉及到的代码比较多,简单总结下吧。
拨打电话时有两处会选择PhoneAccount:

  1. CallsManger的startOutgoingCall(…)方法
    这里会选择一个合适的PhoneAccount,并传进Call对象内。
  2. CreateConnectionProcessor的process()方法
    当使用一个PhoneAccount创建connection失败时,这里会对所有可用的PhoneAccount进行尝试。

5. ConnectionService

An abstract service that should be implemented by any apps which either:

Can make phone calls (VoIP or otherwise) and want those calls to be integrated into the built-in phone app. Referred to as a system managed ConnectionService.
Are a standalone calling app and don’t want their calls to be integrated into the built-in phone app. Referred to as a self managed ConnectionService.

这里的built-in phone app指的应该是TeleService.apk, 而实现ConnectionService的app应该是指包含了类似TeleService.apk功能的apk。 这种apk不想使用TeleService.apk,但是想使用Telecom framework中的call 和audio routing功能,这种apk 创建的connection是不会显示在in-call ui的(会显示在蓝牙设备上),需要apk自己为用户提供GUI。

猜你喜欢

转载自blog.csdn.net/Dylan_Sen/article/details/78899893