Telcom系统


前言

  TelecomManager是获取电话状态、电话账号,提供拨打电话等操作的中枢,位于frameworks/base/telecomm/java/android/telecom/TelecomManager.java
,属于android sdk的API中的一员,供上层APP来使用。

TelecomManager框架

  使用Android的C/S框架:
C为左侧的TelecomManager,S为右侧的Telecom;下文分为这两部分进行介绍

整体框架

客户端–TelecomManager

  分为两部进行介绍:
1. App如何使用该客户端?
2. 客户端如何与服务端通信?

App如何使用该客户端?

  即上图中的调用。这里我们通过TelecomManager的代码进行分析:
路径为:frameworks/base/telecomm/java/android/telecom/TelecomManager.java

    获取TelecomManager对象的方法
    /**
     * @hide
     */
    public static TelecomManager from(Context context) {
        return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
    }

    构造方法
    /**
     * @hide
     */
    public TelecomManager(Context context) {
        Context appContext = context.getApplicationContext();
        if (appContext != null) {
            mContext = appContext;
        } else {
            mContext = context;
        }
    }

  由于构造方法及from获取TelecomManager方法为hide类型,只有被系统应用调用,根据from方法的实现,可以得知普通APP可以使用如下方式进行获取TelecomManager对象(Ps:为什么这个使用,怎么是获取系统服务呢?context.getSystemService;这个在介绍服务端进行解答):

    final TelecomManager tm =
        (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);

  获取TelecomManager对象后就可以进行接口调用,TelecomManager提供了丰富的接口:
查阅SDK文档可以得出:

    //Public Methods

    void     cancelMissedCallsNotification()
    //Removes the missed-call notification if one is present.
    boolean  handleMmi(String dialString)
    //Processes the specified dial string as an MMI code.
    boolean  isInCall()
    //Returns whether there is an ongoing phone call (can be in dialing, ringing, active or holding states).
    void     showInCallScreen(boolean showDialpad)
    //Brings the in-call screen to the foreground if there is an ongoing call

  上述为SDK中介绍的方法,查看源码可以发现,它还提供了更多的方法(qcom 和 MTK有少许的差别),下面主要罗列下MTK下该Manager提供的方法:

TelecomManager方法

  上层APP获取到TelecomManger对象后,直接进行方法调用即可,当然APP一定要具备这些函数要求的权限:例如:

      tm.isInCall();//获取当前是否正在通话
      tm.isRinging();//获取当前是否正在响铃
      ... ...

客户端如何与服务端通信?

  App层中通过该Manager的方法进行特定功能的调用,那具体该Manager中的方法是如何实现的的呢,下面我们拿placeCall()方法进行分析,该方法具体代码为:

    /**
      * Places a new outgoing call to the provided address using the system telecom service with
      * the specified extras.
      *
      * This method is equivalent to placing an outgoing call using {@link Intent#ACTION_CALL},
      * except that the outgoing call will always be sent via the system telecom service. If
      * method-caller is either the user selected default dialer app or preloaded system dialer
      * app, then emergency calls will also be allowed.
      *
      * Requires permission: {@link android.Manifest.permission#CALL_PHONE}
      *
      * Usage example:
      * <pre>
      * Uri uri = Uri.fromParts("tel", "12345", null);
      * Bundle extras = new Bundle();
      * extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
      * telecomManager.placeCall(uri, extras);
      * </pre>
      *
      * The following keys are supported in the supplied extras.
      * <ul>
      *   <li>{@link #EXTRA_OUTGOING_CALL_EXTRAS}</li>
      *   <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
      *   <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>
      *   <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>
      * </ul>
      *
      * @param address The address to make the call to.
      * @param extras Bundle of extras to use with the call.
      */
      public void placeCall(Uri address, Bundle extras) {
        // 获取ITelecomService对象
        ITelecomService service = getTelecomService();
        if (service != null) {
          if (address == null) {
              Log.w(TAG, "Cannot place call to empty address.");
            }
            try {
              // 调用该对象方法执行
              service.placeCall(address, extras == null ? new Bundle() : extras,
                      mContext.getOpPackageName());
                      } catch (RemoteException e) {
                      Log.e(TAG, "Error calling ITelecomService#placeCall", e);
                      }
                  }
      }

  主要分为两步获取ITelecomService对象,调用该对象方法执行:
* 获取ITelecomService对象
具体代码为:

      private ITelecomService getTelecomService() {
          return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
          }

看代码可以的得知是通过AIDL获取服务端的对象,即第一节中框架中的服务端的Telecom;下面的一节主要去讲Telcom服务的初始化及绑定。
* 执行:

      service.placeCall(address, extras == null ? new Bundle() : extras,
        mContext.getOpPackageName());

根据AIDL中定义的接口形式,传递相应参数,通过Binder(AIDL)通知服务端进行实际的操作;服务端如何进行操作也是在下节Telecom服务中进行详解。


服务端–Telecom

Telecom的庐山真面目

  根据Context.TELECOM_SERVICE我们得知服务名称如下:

      public static final String TELECOM_SERVICE = "telecom";

我们使用dumpsys命令在终端查看该服务的详细内容:

命令:adb shell dumpsys activity services

找到telecom结果:

telecom服务

根据打印结果可以得出:
* binder为com.android.server.telecom.TelecomServiceImpl即服务绑定后的返回IBinder;
* cmp为com.android.server.telecom/.components.TelecomService
* 位置:

baseDir=/system/priv-app/Telecom/Telecom.apk
dataDir=/data/user/0/com.android.server.telecom

Telecom的初始化

  从上述打印信息可以看出Telecom服务是在Telecom.apk中,而该app源码位置为packages/services/Telecomm/
打开该Telecomm的AndroidManifest查看可以发现注册的service与打印相符,到现在为止我们已经找到该服务的源码位置
packages/services/Telecomm/src/com/com.android.server.telecom/TelecomService.java

<service android:name=".components.TelecomService"
        android:singleUser="true"
        android:process="system">
    <intent-filter>
        <action android:name="android.telecom.ITelecomService" />
    </intent-filter>
</service>

  找到服务定义的地方,那么该服务是什么时候被启动和绑定的呢,由于telecom属于系统服务,系统服务的添加一般在SystemServer中进行添加和启动;
源码位置为:framework/base/services/java/com/android/server/SystemServer.java;具体启动代码如下:

// Start services.
try {
    Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartServices");
    startBootstrapServices();
    startCoreServices();
    startOtherServices();
} catch (Throwable ex) {
    Slog.e("System", "******************************************");
    Slog.e("System", "************ Failure starting system services", ex);
    /// M: RecoveryManagerService  @{
    if (mRecoveryManagerService != null && ex instanceof RuntimeException) {
        mRecoveryManagerService.handleException((RuntimeException) ex, true);
    } else {
        throw ex;
    }
    /// @}
} finally {
    Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}

  查看源码Telecom服务是在startOtherServices()中被添加启动;其实系统服务启动的流程大致分为三个步骤:
1. 初始化Service 对象,获得IBinder对象。

  1. 启动后台线程,并进入Loop等待。

  2. 将自己注册到Service Manager, 让其他进程通过名字可以获得远程调用必须的IBinder的对象。

Telecom服务被添加启动的大致时序图如下:

时序图

从流程中可以得出启动服务的核心代码在TelecomLoaderService.java中

@Override
public void onBootPhase(int phase) {
    if (phase == PHASE_ACTIVITY_MANAGER_READY) {
        //  注册监听默认应用程序(电话和短信应用)
        registerDefaultAppNotifier();
        //  注册监听运营商信息发生变化
        registerCarrierConfigChangedReceiver();
        //  启动Telecom并连接
        connectToTelecom();
    }
}

  前面两个函数在这里不做分析,这里主要分析connectToTelecom()函数,主要功能就是建立连接,并将连接后IBinder返回添加到系统服务中:

代码如下:

private void connectToTelecom() {
    synchronized (mLock) {
        if (mServiceConnection != null) {
            // TODO: Is unbinding worth doing or wait for system to rebind?
            mContext.unbindService(mServiceConnection);
            mServiceConnection = null;
        }
        // 构建连接类
        TelecomServiceConnection serviceConnection = new TelecomServiceConnection();

        // 这里的ACTION和SERVICE_COMPONENT即是第二节中Telecom的AndroidManifest中注册的。
        Intent intent = new Intent(SERVICE_ACTION);
        intent.setComponent(SERVICE_COMPONENT);
        int flags = Context.BIND_IMPORTANT | Context.BIND_FOREGROUND_SERVICE
                | Context.BIND_AUTO_CREATE;

        // Bind to Telecom and register the service
        if (mContext.bindServiceAsUser(intent, serviceConnection, flags, UserHandle.OWNER)) {
            mServiceConnection = serviceConnection;
        }
    }
}

  下面主要对TelecomServiceConnection进行分析,源码如下:

private class TelecomServiceConnection implements ServiceConnection {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // Normally, we would listen for death here, but since telecom runs in the same process
        // as this loader (process="system") thats redundant here.
        try {
            service.linkToDeath(new IBinder.DeathRecipient() {
                @Override
                public void binderDied() {
                    connectToTelecom();
                }
            }, 0);
            SmsApplication.getDefaultMmsApplication(mContext, false);
            // 这里将IBinder添加至系统服务中,且服务的TAG为Context.TELECOM_SERVICE</font>
            ServiceManager.addService(Context.TELECOM_SERVICE, service);

            synchronized (mLock) {
            // 对默认短信、电话等app进行权限动态赋予
            ... ...
            ... ...
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed linking to death.");
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        connectToTelecom();
    }
}

  根据服务的跨进程调用可知:服务端中OnBinder()方法将IBinder对象的代理传递给客户端中ServiceConnected的onServiceConnected的第二个参数,即上述代码中的IBinder service;而此时客户端将该对象添加至系统服务中:

// 这里将IBinder添加至系统服务中,且服务的TAG为Context.TELECOM_SERVICE</font>
ServiceManager.addService(Context.TELECOM_SERVICE, service);

以上可以解答第二节客户端为什么那么使用的疑问;

Telecom服务端具体实现

  上面是分析建立连接后客户端获取服务端的代理后进行添加至系统服务的动作;那服务端的OnBinder中函数返回的IBinder对象具体是什么呢?下面再来对服务端进行详细的讲解:
源码位置:packages/services/Telecomm/src/com/com.android.server.telecom/TelecomService.java

@Override
public IBinder onBind(Intent intent) {
    Log.d(this, "onBind");
    // 初始化Telecom系统
    initializeTelecomSystem(this);
    // 返回服务代理给客户端
    synchronized (getTelecomSystem().getLock()) {
        return getTelecomSystem().getTelecomServiceImpl().getBinder();
    }
}

初始化Telecom系统,这里是服务中的核心:未接来电通知栏、Sensor管理等等对象创建:

/**
     * This method is to be called by components (Activitys, Services, ...) to initialize the
     * Telecom singleton. It should only be called on the main thread. As such, it is atomic
     * and needs no synchronization -- it will either perform its initialization, after which
     * the {@link TelecomSystem#getInstance()} will be initialized, or some other invocation of
     * this method on the main thread will have happened strictly prior to it, and this method
     * will be a benign no-op.
     *
     * @param context
     */
    static void initializeTelecomSystem(Context context) {
        if (TelecomSystem.getInstance() == null) {
            TelecomSystem.setInstance(
                    new TelecomSystem(
                            context,
                            new MissedCallNotifierImpl(context.getApplicationContext()),
                            new CallerInfoAsyncQueryFactory() {
                                @Override
                                public CallerInfoAsyncQuery startQuery(int token, Context context,
                                        String number,
                                        CallerInfoAsyncQuery.OnQueryCompleteListener listener,
                                        Object cookie, int subId) {
                                    Log.i(TelecomSystem.getInstance(),
                                            "CallerInfoAsyncQuery.startQuery number=%s cookie=%s",
                                            Log.pii(number), cookie);
                                    return CallerInfoAsyncQuery.startQuery(
                                            token, context, number, listener, cookie, subId);
                                }
                            },
                            new HeadsetMediaButtonFactory() {
                                @Override
                                public HeadsetMediaButton create(
                                        Context context,
                                        CallsManager callsManager,
                                        TelecomSystem.SyncRoot lock) {
                                    return new HeadsetMediaButton(context, callsManager, lock);
                                }
                            },
                            new ProximitySensorManagerFactory() {
                                @Override
                                public ProximitySensorManager create(
                                        Context context,
                                        CallsManager callsManager) {
                                    return new ProximitySensorManager(context, callsManager);
                                }
                            },
                            new InCallWakeLockControllerFactory() {
                                @Override
                                public InCallWakeLockController create(Context context,
                                        CallsManager callsManager) {
                                    return new InCallWakeLockController(context, callsManager);
                                }
                            }));
        }
        if (BluetoothAdapter.getDefaultAdapter() != null) {
            context.startService(new Intent(context, BluetoothPhoneService.class));
        }
    }

下面对Telecom类进行分析,类图如下:

类图

Telecom类中每个对象,对于Telecom整个系统都起到至关重要要的作用:下面将Telcom构造器代码贴在下面,后续文章会针对各个模块进行分析:

public TelecomSystem(
        Context context,
        MissedCallNotifier missedCallNotifier,
        CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
        HeadsetMediaButtonFactory headsetMediaButtonFactory,
        ProximitySensorManagerFactory proximitySensorManagerFactory,
        InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
    mContext = context.getApplicationContext();

    // 未接来电通知管理
    mMissedCallNotifier = missedCallNotifier;
    // 通话账号注册监听类
    mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
    // 联系人同步类
    mContactsAsyncHelper = new ContactsAsyncHelper(mLock);

    // 通话状态管理中心,是整个通话上层逻辑的枢纽
    mCallsManager = new CallsManager(
            mContext,
            mLock,
            mContactsAsyncHelper,
            callerInfoAsyncQueryFactory,
            mMissedCallNotifier,
            mPhoneAccountRegistrar,
            headsetMediaButtonFactory,
            proximitySensorManagerFactory,
            inCallWakeLockControllerFactory);

    // 短信快速回复管理
    mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
    mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);

    mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
    mBluetoothPhoneServiceImpl = new BluetoothPhoneServiceImpl(
            mContext, mLock, mCallsManager, mPhoneAccountRegistrar);
    // 通话Intent处理类
    mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager);
    mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
            mContext, mCallsManager);

    // Telecom服务的代理,返回给客户端
    mTelecomServiceImpl = new TelecomServiceImpl(
            mContext, mCallsManager, mPhoneAccountRegistrar, mLock);
}

分析完initializeTelecomSystem,那么我们来到返回的IBinder:

synchronized (getTelecomSystem().getLock()) {
    return getTelecomSystem().getTelecomServiceImpl().getBinder();

    public TelecomSystem getTelecomSystem() {
    return TelecomSystem.getInstance();
}

从Telecom的类中getTelecomServiceImpl返回是TelecomServiceImpl对象,TelecomServiceImpl的getBinder()的实现如下:

public ITelecomService.Stub getBinder() {
    return mBinderImpl;
}

那么mBinderImpl就是我们IBinder,客户端与服务通信的枢纽,具体实现如下:

private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
        @Override
        public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme,
                String callingPackage) {
            synchronized (mLock) {
                if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
                    return null;
                }

                long token = Binder.clearCallingIdentity();
                try {
                    PhoneAccountHandle defaultOutgoingPhoneAccount =
                            mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(uriScheme);
                    // Make sure that the calling user can see this phone account.
                    // TODO: Does this isVisible check actually work considering we are clearing
                    // the calling identity?
                    if (defaultOutgoingPhoneAccount != null
                            && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
                        Log.w(this, "No account found for the calling user");
                        return null;
                    }
                    return defaultOutgoingPhoneAccount;
                } catch (Exception e) {
                    Log.e(this, e, "getDefaultOutgoingPhoneAccount");
                    throw e;
                } finally {
                    Binder.restoreCallingIdentity(token);
                }
            }
        }
        // ITelecomService中函数的具体实现
        ... ...
        ... ...
        /**
         * @see android.telecom.TelecomManager#placeCall
         */
        @Override
        public void placeCall(Uri handle, Bundle extras, String callingPackage) {
            enforceCallingPackage(callingPackage);
            if (!canCallPhone(callingPackage, "placeCall")) {
                throw new SecurityException("Package " + callingPackage
                        + " is not allowed to place phone calls");
            }

            // Note: we can still get here for the default/system dialer, even if the Phone
            // permission is turned off. This is because the default/system dialer is always
            // allowed to attempt to place a call (regardless of permission state), in case
            // it turns out to be an emergency call. If the permission is denied and the
            // call is being made to a non-emergency number, the call will be denied later on
            // by {@link UserCallIntentProcessor}.

            final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
                    Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;

            final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
                    PackageManager.PERMISSION_GRANTED;

            synchronized (mLock) {
                final UserHandle userHandle = Binder.getCallingUserHandle();
                long token = Binder.clearCallingIdentity();
                try {
                    final Intent intent = new Intent(Intent.ACTION_CALL, handle);
                    intent.putExtras(extras);
                    new UserCallIntentProcessor(mContext, userHandle).processIntent(intent,
                            callingPackage, hasCallAppOp && hasCallPermission);
                } finally {
                    Binder.restoreCallingIdentity(token);
                }
            }
        }
        ... ...
        ... ...
    };

这里就是服务端的具体实现:我们通过TelecomManger.placeCall()方法最终的实现就是上述的placeCall完成的:

// 拨打电话,最终通过UserCallIntentProcessor进行拨打电话,然后建立通话链接进入拨号的流程。
new UserCallIntentProcessor(mContext, userHandle).processIntent(intent,
        callingPackage, hasCallAppOp && hasCallPermission);

总结

至此关于整个Telecom系统的分析介绍吿一段落。文中如有差错,欢迎大家指正,一起学习。后续会继续更新Telcom每个模块的详细分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值