车机蓝牙通话流程分析的流程分析

8 篇文章 19 订阅 ¥9.90 ¥99.00

PlantUML Web Server

部分内容参照Android HeadSetClient端通话的传递_天花板之恋的博客-CSDN博客

Android源代码中,如果通话状态有改变,会沿着这样的顺序传递:
蓝牙chip >> HCI接口 >> BlueDroid协议栈 >> Bluetooth >> 广播传递 >> Telecom ,

2. bluetooth 上层流程分析

2.1 收到JNI回调

通话状态有改变,会通过NativeInterface这个类里面的onCallSetup方法回调通知:

public class NativeInterface {
	.........
	private void onCallSetup(int callsetup, byte[] address) {  
        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALLSETUP);
        event.valueInt = callsetup;
        event.device = getDevice(address);
        HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
		
		service.messageFromNative(event);    //1.通话状态改变的消息给到HeadsetClientService
       
    }
    ........
}

我们以HF端发起拨号请求为例,那么最开始回调的状态就是CALL_STATE_DIALING,callsetup的值为2。然后NativeInterface会把消息封装成一个StackEvent类型的数据结构,给到HeadsetClientService去处理。

2.2 service 封装消息

在HeadsetClientService中,对于协议栈上来的数据,它只是做一个转接,会把消息给到对应的状态机HeadsetClientStateMachine处理:

public class HeadsetClientService extends ProfileService {
		........
	public void messageFromNative(StackEvent stackEvent) {
        HeadsetClientStateMachine sm = getStateMachine(stackEvent.device);

        sm.sendMessage(StackEvent.STACK_EVENT, stackEvent);  //2.消息交给状态机处理
    }
	........
}

2.3状态机处理

在状态机中,此时状态正常情况下是处于Connected状态,看看对此消息的处理:

class Connected extends State {
	......
	case StackEvent.EVENT_TYPE_CALLSETUP:
	
	sendMessage(QUERY_CURRENT_CALLS);  // 3.这里仅是给自己发一个QUERY_CURRENT_CALLS的消息
                            break;

好吧,这里状态机只是给自己发了一条QUERY_CURRENT_CALLS的消息,让自己去查询当前的通话状态:

case QUERY_CURRENT_CALLS:
                    removeMessages(QUERY_CURRENT_CALLS);
                    if (mCalls.size() > 0) {
                        // If there are ongoing calls periodically check their status.
                        sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);  
                        //3.1这里值得注意,如果已经存在通话,那么就会定期查询通话状态
                    }
                    queryCallsStart();
                    break;						
private boolean queryCallsStart() {
        clearPendingAction();
        mNativeInterface.queryCurrentCalls(getByteAddress(mCurrentDevice));  //3.2调用JNI方法去查询当前的远程设备的通话
        addQueuedAction(QUERY_CURRENT_CALLS, 0);                                                   //3.3这里会在mQueuedActions消息队列里添加一条记录
        return true;
    }

最后调用了NativeInterface提供的JNI方法,去查询对应设备的通话状态。

2.4 在执行了通话状态查询的请求指令,AG端反馈状态后,首先会回调NativeInterface的onCurrentCalls这个方法:

private void onCurrentCalls(int index, int dir, int state, int mparty, String number,  
            byte[] address) {
			
	    StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS);
        event.valueInt = index;
        event.valueInt2 = dir;
        event.valueInt3 = state;
        event.valueInt4 = mparty;
        event.valueString = number;
        event.device = getDevice(address);
      
        HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
      
        service.messageFromNative(event);
     
	}

同样是给到HeadsetClientService,然后再给到HeadsetClientStateMachine处理:

class Connected extends State {
		......
	 case StackEvent.EVENT_TYPE_CURRENT_CALLS:  
                   queryCallsUpdate(event.valueInt, event.valueInt3, event.valueString,
                   event.valueInt4  == HeadsetClientHalConstants.CALL_MPTY_TYPE_MULTI,
                   event.valueInt2  == HeadsetClientHalConstants.CALL_DIRECTION_OUTGOING);
                   break;
   	    ......
   }

通话状态的查询结果处理,只是在mCallsUpdate 这个map中添加一个BluetoothHeadsetClientCall对象,没有再继续传递下去。

2.5 

在onCurrentCalls这个方法之后,还会回调onCmdResult这个方法,它反馈的是HF端请求指令的执行结果,这里对应我们刚刚发起的查询当前通话状态的请求:

private void onCmdResult(int type, int cme, byte[] address) {
        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);  
        event.valueInt = type;
        event.valueInt2 = cme;
        event.device = getDevice(address);
      
        HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
    
        service.messageFromNative(event);
   
    }

同样是给到HeadsetClientService,然后再给到HeadsetClientStateMachine处理:

case StackEvent.EVENT_TYPE_CMD_RESULT:   
		
							Pair<Integer, Object> queuedAction = mQueuedActions.poll();   
							//从请求队列中取出队头的一条数据(之前我们往队列里放了数据)
							
							switch (queuedAction.first) {
                                case QUERY_CURRENT_CALLS:
                                    queryCallsDone();                       //代表查询所有通话的指令已经完成
                                    break;
									.......
							}
							
							break;
 private void queryCallsDone() {
 
 		......
		// Add the new calls.
        for (Integer idx : callAddedIds) {   //对于新增加的通话,会走到这里
            BluetoothHeadsetClientCall c = mCallsUpdate.get(idx);
            mCalls.put(idx, c);                         //把这个新增加通话放到mCalls里面
            sendCallChangedIntent(c);      //把通话改变的消息广播出去
        }
 
 }
 private void sendCallChangedIntent(BluetoothHeadsetClientCall c) {  //把通话改变后的状态广播出去
 
        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c);
        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);  
}

可以看到,最后的通话状态是通过广播的形式传递出去的,BluetoothHeadsetClientCall继承自Parcelable接口,是可以实现序列化传递的。
如果我们要实现一个蓝牙电话的功能,那么直接接收BluetoothHeadsetClient.ACTION_CALL_CHANGED这个广播就可以获取到通话的状态。

在Bluetooth内部,有一个HfpClientConnectionService的类,在HeadsetClientService初始化的时候,就会把它调起,如下:

// Start the HfpClientConnectionService to create connection with telecom when HFP
        // connection is available.
        Intent startIntent = new Intent(this, HfpClientConnectionService.class);
        startService(startIntent);

可以看出,它是连接HFP和Telecom的桥梁。
在它的内部,也实现了BluetoothHeadsetClient.ACTION_CALL_CHANGED这个广播的接收处理,并且把状态传递给Telecom:

if (BluetoothHeadsetClient.ACTION_CALL_CHANGED.equals(action)) {
                BluetoothHeadsetClientCall call =
                        intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL);
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                HfpClientDeviceBlock block = findBlockForDevice(call.getDevice());
                
                block.handleCall(call);
            }

看到HfpClientDeviceBlock里面的handleCall方法:

synchronized void handleCall(BluetoothHeadsetClientCall call) {

	HfpClientConnection connection = findConnectionKey(call);
	
	if (connection != null) {
            connection.updateCall(call);
            connection.handleCallChanged();     //这里就通过之前创建的连接,去把通话改变后的状态给到Telecom
        }

	if (connection == null) {     //第一次的通话变化会走到这里
		 // Create the connection here, trigger Telecom to bind to us.
            buildConnection(call, null);//创建一个连接
            
             mTelecomManager.addNewUnknownCall(mPhoneAccount.getAccountHandle(), b);//告诉Telecom有新的通话
	}

}

3.telecom 处理逻辑

1. telecomManager 开始处理事件

    @SystemApi
    public void addNewUnknownCall(PhoneAccountHandle phoneAccount, Bundle extras) {
        try {
            if (isServiceConnected()) {
                getTelecomService().addNewUnknownCall(
                        phoneAccount, extras == null ? new Bundle() : extras);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException adding a new unknown call: " + phoneAccount, e);
        }
    }

具体是实现在 TelecomServiceImpl

        @Override
        public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
                。。。

                synchronized (mLock) {
                    if (phoneAccountHandle != null &&
                            phoneAccountHandle.getComponentName() != null) {
                        mAppOpsManager.checkPackage(
                                Binder.getCallingUid(),
                                phoneAccountHandle.getComponentName().getPackageName());

                        // Make sure it doesn't cross the UserHandle boundary
                        enforceUserHandleMatchesCaller(phoneAccountHandle);
                        enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
                                Binder.getCallingUserHandle());
                        long token = Binder.clearCallingIdentity();

                        try {
                            Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
                            if (extras != null) {
                                extras.setDefusable(true);
                                intent.putExtras(extras);
                            }
                            intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
                            intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
                                    phoneAccountHandle);
   // 发送intent
                            mCallIntentProcessorAdapter.processUnknownCallIntent(mCallsManager, intent);
                        } finally {
                            Binder.restoreCallingIdentity(token);
                        }
                    } else {
                        Log.i(this,
                                "Null phoneAccountHandle or not initiated by Telephony. " +
                                        "Ignoring request to add new unknown call.");
                    }
                  。。。

        }

CallIntentProcessor  将事件交给 callmanager 处理。

    static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);

        if (phoneAccountHandle == null) {
            Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
            return;
        }
        if (phoneAccountHandle.getComponentName() == null) {
            Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
            return;
        }

        callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
    }

callManager 会新建一个call 事件, 

    void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
        Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE);
        Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
        Call call = new Call(
                getNextCallId(),
                mContext,
                this,
                mLock,
                mConnectionServiceRepository,
                mPhoneNumberUtilsAdapter,
                handle,
                null /* gatewayInfo */,
                null /* connectionManagerPhoneAccount */,
                phoneAccountHandle,
                Call.CALL_DIRECTION_UNKNOWN /* callDirection */,
                // Use onCreateIncomingConnection in TelephonyConnectionService, so that we attach
                // to the existing connection instead of trying to create a new one.
                true /* forceAttachToExistingConnection */,
                false, /* isConference */
                mClockProxy);
        call.initAnalytics();  

        setIntentExtrasAndStartTime(call, extras);
        call.addListener(this);  
        call.startCreateConnection(mPhoneAccountRegistrar);
    }

Call 会创建CreateConnectionProcessor 处理

    void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
        if (mCreateConnectionProcessor != null) {
            Log.w(this, "mCreateConnectionProcessor in startCreateConnection is not null. This is" +
                    " due to a race between NewOutgoingCallIntentBroadcaster and " +
                    "phoneAccountSelected, but is harmlessly resolved by ignoring the second " +
                    "invocation.");
            return;
        }
        mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
                phoneAccountRegistrar, mContext);
        mCreateConnectionProcessor.process();
    }
CreateConnectionProcessor 
    public void process() {
        Log.v(this, "process");
        clearTimeout();
        mAttemptRecords = new ArrayList<>();
        if (mCall.getTargetPhoneAccount() != null) {
            mAttemptRecords.add(new CallAttemptRecord(
                    mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount()));
        }
        if (!mCall.isSelfManaged()) {
            adjustAttemptsForConnectionManager();
            adjustAttemptsForEmergency(mCall.getTargetPhoneAccount());
        }
        mAttemptRecordIterator = mAttemptRecords.iterator();
        attemptNextPhoneAccount();
    }

    private void attemptNextPhoneAccount() {
        Log.v(this, "attemptNextPhoneAccount");
        CallAttemptRecord attempt = null;
        if (mAttemptRecordIterator.hasNext()) {
            attempt = mAttemptRecordIterator.next();

            if (!mPhoneAccountRegistrar.phoneAccountRequiresBindPermission(
                    attempt.connectionManagerPhoneAccount)) {
                Log.w(this,
                        "Connection mgr does not have BIND_TELECOM_CONNECTION_SERVICE for "
                                + "attempt: %s", attempt);
                attemptNextPhoneAccount();
                return;
            }

            // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it
            // also requires the BIND_TELECOM_CONNECTION_SERVICE permission.
            if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) &&
                    !mPhoneAccountRegistrar.phoneAccountRequiresBindPermission(
                            attempt.targetPhoneAccount)) {
                Log.w(this,
                        "Target PhoneAccount does not have BIND_TELECOM_CONNECTION_SERVICE for "
                                + "attempt: %s", attempt);
                attemptNextPhoneAccount();
                return;
            }
        }

        if (mCallResponse != null && attempt != null) {
            Log.i(this, "Trying attempt %s", attempt);
            PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
            mService = mRepository.getService(phoneAccount.getComponentName(),
                    phoneAccount.getUserHandle());
            if (mService == null) {
                Log.i(this, "Found no connection service for attempt %s", attempt);
                attemptNextPhoneAccount();
            } else {
                mConnectionAttempt++;
                mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
                mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
                mCall.setConnectionService(mService);
                setTimeoutIfNeeded(mService, attempt);
                if (mCall.isIncoming()) {
                    mService.createConnection(mCall, CreateConnectionProcessor.this);
                } else {
                    // Start to create the connection for outgoing call after the ConnectionService
                    // of the call has gained the focus.
                    mCall.getConnectionServiceFocusManager().requestFocus(
                            mCall,
                            new CallsManager.RequestCallback(new CallsManager.PendingAction() {
                                @Override
                                public void performAction() {
                                    Log.d(this, "perform create connection");
                                    mService.createConnection(
                                            mCall,
                                            CreateConnectionProcessor.this);
                                }
                            }));

                }
            }
        } else {
            Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
            DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ?
                    mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR);
            notifyCallConnectionFailure(disconnectCause);
        }
    }

 ???????????????

ConnectionServiceWrapper 这里会执行createConnection 操作
        @Override
        public void handleCreateConnectionComplete(String callId, ConnectionRequest request,
                ParcelableConnection connection, Session.Info sessionInfo) {
            Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
            long token = Binder.clearCallingIdentity();
            try {
                synchronized (mLock) {
                    logIncoming("handleCreateConnectionComplete %s", callId);
                    ConnectionServiceWrapper.this
                            .handleCreateConnectionComplete(callId, request, connection);

                    if (mServiceInterface != null) {
                        logOutgoing("createConnectionComplete %s", callId);
                        try {
                            mServiceInterface.createConnectionComplete(callId,
                                    Log.getExternalSession());
                        } catch (RemoteException e) {
                        }
                    }
                }
            } catch (Throwable t) {
                Log.e(ConnectionServiceWrapper.this, t, "");
                throw t;
            } finally {
                Binder.restoreCallingIdentity(token);
                Log.endSession();
            }
        }

调用Call.java 的handleCreateConnectionSuccess

    @Override
    public void handleCreateConnectionSuccess(
            CallIdMapper idMapper,
            ParcelableConnection connection) {
        Log.d(this, "handleCreateConnectionSuccessful %s", connection);
        setTargetPhoneAccount(connection.getPhoneAccount());
        setHandle(connection.getHandle(), connection.getHandlePresentation());
        setCallerDisplayName(
                connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());

        setConnectionCapabilities(connection.getConnectionCapabilities());
        setConnectionProperties(connection.getConnectionProperties());
        setIsVoipAudioMode(connection.getIsVoipAudioMode());
        setSupportedAudioRoutes(connection.getSupportedAudioRoutes());
        setVideoProvider(connection.getVideoProvider());
        setVideoState(connection.getVideoState());
        setRingbackRequested(connection.isRingbackRequested());
        setStatusHints(connection.getStatusHints());
        putExtras(SOURCE_CONNECTION_SERVICE, connection.getExtras());

        mConferenceableCalls.clear();
        for (String id : connection.getConferenceableConnectionIds()) {
            mConferenceableCalls.add(idMapper.getCall(id));
        }

        switch (mCallDirection) {
            case CALL_DIRECTION_INCOMING:
                // Listeners (just CallsManager for now) will be responsible for checking whether
                // the call should be blocked.
                for (Listener l : mListeners) {
                    l.onSuccessfulIncomingCall(this);
                }
                break;
            case CALL_DIRECTION_OUTGOING:
                for (Listener l : mListeners) {
                    l.onSuccessfulOutgoingCall(this,
                            getStateFromConnectionState(connection.getState()));
                }
                break;
            case CALL_DIRECTION_UNKNOWN:
                for (Listener l : mListeners) {
                    l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection
                            .getState()));
                }
                break;
        }
    }

接着  CallsManager 的 onSuccessfulUnknownCall() 到 addCall()

    public void addCall(Call call) {
        Trace.beginSection("addCall");
        Log.d(this, "addCall(%s)", call);
        call.addListener(this);
        mCalls.add(call);

        // Specifies the time telecom finished routing the call. This is used by the dialer for
        // analytics.
        Bundle extras = call.getIntentExtras();
        extras.putLong(TelecomManager.EXTRA_CALL_TELECOM_ROUTING_END_TIME_MILLIS,
                SystemClock.elapsedRealtime());

        updateCanAddCall();
        // onCallAdded for calls which immediately take the foreground (like the first call).
        for (CallsManagerListener listener : mListeners) {
            if (LogUtils.SYSTRACE_DEBUG) {
                Trace.beginSection(listener.getClass().toString() + " addCall");
            }
            listener.onCallAdded(call);
            if (LogUtils.SYSTRACE_DEBUG) {
                Trace.endSection();
            }
        }
        Trace.endSection();
    }
InCallController 这个对象是连接类
    @Override
    public void onCallAdded(Call call) {
        if (!isBoundAndConnectedToServices()) {
            Log.i(this, "onCallAdded: %s; not bound or connected.", call);
            // We are not bound, or we're not connected.
            bindToServices(call);
        } else {
            // We are bound, and we are connected.
            adjustServiceBindingsForEmergency();

            // This is in case an emergency call is added while there is an existing call.
            mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
                    mCallsManager.getCurrentUserHandle());

            Log.i(this, "onCallAdded: %s", call);
            // Track the call if we don't already know about it.
            addCall(call);

            Log.i(this, "mInCallServiceConnection isConnected=%b",
                    mInCallServiceConnection.isConnected());

            List<ComponentName> componentsUpdated = new ArrayList<>();
            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
                InCallServiceInfo info = entry.getKey();

                if (call.isExternalCall() && !info.isExternalCallsSupported()) {
                    continue;
                }

                if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
                    continue;
                }

                // Only send the RTT call if it's a UI in-call service
                boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());

                componentsUpdated.add(info.getComponentName());
                IInCallService inCallService = entry.getValue();

                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
                        true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
                        info.isExternalCallsSupported(), includeRttCall,
                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
                try {
                    inCallService.addCall(parcelableCall);
                } catch (RemoteException ignored) {
                }
            }
            Log.i(this, "Call added to components: %s", componentsUpdated);
        }
    }

其中 bindToServices

    public void bindToServices(Call call) {
        if (mInCallServiceConnection == null) {
            InCallServiceConnection dialerInCall = null;
            InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
            Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
            if (defaultDialerComponentInfo != null &&
                    !defaultDialerComponentInfo.getComponentName().equals(
                            mSystemInCallComponentName)) {
                dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
            }
            Log.i(this, "defaultDialer: " + dialerInCall);

            InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
                    mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI);
            EmergencyInCallServiceConnection systemInCall =
                    new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
            systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());

            InCallServiceConnection carModeInCall = null;
            InCallServiceInfo carModeComponentInfo = getCarModeComponent();
            if (carModeComponentInfo != null &&
                    !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
                carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
            }

            mInCallServiceConnection =
                    new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
        }

        mInCallServiceConnection.setCarMode(shouldUseCarModeUI());

        // Actually try binding to the UI InCallService.  If the response
        if (mInCallServiceConnection.connect(call) ==
                InCallServiceConnection.CONNECTION_SUCCEEDED) {
            // Only connect to the non-ui InCallServices if we actually connected to the main UI
            // one.
            connectToNonUiInCallServices(call);
            mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
                    mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                            mContext.getContentResolver()),
                    TimeUnit.MILLISECONDS);
        } else {
            Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
        }
    }

InCallController
  // 这个方法会绑定默认的电话应用
    public void bindToServices(Call call) {
        if (mInCallServiceConnection == null) {
            InCallServiceConnection dialerInCall = null;
            InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
            Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
            if (defaultDialerComponentInfo != null &&
                    !defaultDialerComponentInfo.getComponentName().equals(
                            mSystemInCallComponentName)) {
                dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
            }
            Log.i(this, "defaultDialer: " + dialerInCall);

            InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
                    mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI);
            EmergencyInCallServiceConnection systemInCall =
                    new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
            systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());

            InCallServiceConnection carModeInCall = null;
            InCallServiceInfo carModeComponentInfo = getCarModeComponent();
            if (carModeComponentInfo != null &&
                    !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
                carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
            }

            mInCallServiceConnection =
                    new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
        }

        mInCallServiceConnection.setCarMode(shouldUseCarModeUI());

        // Actually try binding to the UI InCallService.  If the response
        if (mInCallServiceConnection.connect(call) ==
                InCallServiceConnection.CONNECTION_SUCCEEDED) {
            // Only connect to the non-ui InCallServices if we actually connected to the main UI
            // one.
            connectToNonUiInCallServices(call);
            mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
                    mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                            mContext.getContentResolver()),
                    TimeUnit.MILLISECONDS);
        } else {
            Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
        }
    }

走到这里看到有个defaultDialerApp 这个就是默认的电话应用

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 车机蓝牙电话分析图是一种使用UML(统一建模语言)来描述车机蓝牙电话系统的图形化表示方法。该分析图主要用于展示系统中的各个组件、类、关系和交互过程,以便开发人员或设计人员更好地理解系统的结构和行为。 在车机蓝牙电话分析图中,通常会包含以下几个主要部分: 1. 类图:用于展示系统中各个类及其之间的关系。类图描述了系统中的类以及它们的属性和方法,并通过关联、聚合、继承等关系来表示类之间的交互。 2. 时序图:用于展示系统中各个对象之间的交互过程和消息传递顺序。时序图描述了一系列交互的对象以及它们之间发送的消息,可以清晰地展示系统中各个对象的活动顺序和时序关系。 3. 用例图:用于展示系统中的各个用例及其之间的关系。用例图描述了系统的功能和服务,并通过参与者和用例之间的关系展示参与者与系统的交互。 通过使用车机蓝牙电话分析图,可以更好地理解车机蓝牙电话系统的结构和行为,并在开发过程中指导系统的设计和实现。它可以帮助团队成员之间更好地沟通和协作,并且能够更好地满足用户需求和系统设计要求。 总之,车机蓝牙电话分析图是一种有效的建模工具,它能够帮助开发人员和设计人员更好地理解和设计车机蓝牙电话系统。 ### 回答2: 车机蓝牙电话分析图是一种使用统一建模语言(UML)来描述车载蓝牙电话系统的模型表示方法。这个分析图用于揭示系统中各个组件、类和它们之间的关联关系。 在车机蓝牙电话分析图的建模过程中,可以包含以下几个主要的组件和类: 1. 蓝牙模块:表示车载系统中的蓝牙功能模块,负责处理蓝牙连接、音频传输等功能。通过该模块实现与手机的通信。 2. 手机:表示用户的蓝牙手机设备,与车载系统通过蓝牙进行通信,实现电话接听、拨打、挂断等功能。 3. 电话模块:表示车载系统中的电话功能模块,负责处理电话呼叫、通话控制等功能。通过该模块实现与手机的电话通信。 4. 音频处理类:表示车机蓝牙电话系统中的音频处理功能,负责处理接收到的音频数据和发出的音频数据。 5. 用户界面:表示车载系统上的用户界面,提供操作菜单、电话簿选择和呼叫控制等功能。 在车机蓝牙电话分析图中,这些组件和类之间的关联关系可以通过关联关系、聚合关系和组合关系等方式进行表示。例如,蓝牙模块和手机之间可以建立关联关系,表示它们之间的通信连接;蓝牙模块和电话模块之间可以建立聚合关系,表示蓝牙模块包含了电话模块。 通过车机蓝牙电话分析图,可以清晰地描述车载系统中车机蓝牙电话的整体结构和各个组件之间的关系,有助于开发人员进行系统设计和开发工作。同时,这样的分析图还可以作为系统设计文档的一部分,方便后续的维护和升级工作。 ### 回答3: 车机蓝牙电话分析图(UML)是一种用于描述车机系统中蓝牙电话功能的图表表示方法。它使用统一建模语言(UML)的符号和术语来展示车机系统中不同组件之间的交互和关系。 在车机蓝牙电话分析图中,主要包括以下几个重要的元素: 1. 蓝牙电话设备(Phone Device):代表车辆中的蓝牙电话设备,可以是手机或其他蓝牙兼容设备。它与车机系统通过蓝牙连接进行通信。 2. 车机系统(Infotainment System):代表车辆中的车机系统,负责处理蓝牙电话的连接和通信功能。它与蓝牙电话设备进行数据交换,并将通话和音频功能连接到车辆的音频系统。 3. 用户界面(User Interface):代表车机系统中的用户界面,可以是显示屏、按钮或触摸屏。它与用户进行交互,接收和显示来自蓝牙电话设备的通知、通话状态和控制命令。 4. 蓝牙电话连接(Bluetooth Connection):代表车机系统与蓝牙电话设备之间的蓝牙连接。它负责建立和维护蓝牙通信链路,并支持电话的呼入和呼出功能。 5. 电话操作(Phone Operation):代表车机系统中与电话功能相关的操作,包括拨打电话、接听电话、挂断电话和静音等。它接收用户输入并将命令传递给蓝牙电话设备。 通过分析车机蓝牙电话分析图,可以清楚地了解车机系统中蓝牙电话功能的工作原理和不同组件之间的交互方式。这有助于开发人员设计和实现车机系统中的蓝牙电话功能,并确保其正常运行和用户友好性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值