AndroidN去电流程中和InCallUI的交互(拉起界面过程分析)

2019-04-13 17:09发布

  在分析android N拨打电话流程中分析了,从拨号盘到RIL.java的流程,没有分析拨号过程和UI的交互,这篇文章来说明,拉起InCallUI的过程.   在上篇文章中可以知道CallIntentProcessor中会通过processOutgoingCallIntent来调用CallManager的startOutgoingCall和NewOutgoingCallIntentBroadcaster的processIntent方法,分析就是从startOutgoingCall开始的. 1.CallManager的startOutgoingCall Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras, UserHandle initiatingUser) { boolean isReusedCall = true; Call call = reuseOutgoingCall(handle); // Create a call with original handle. The handle may be changed when the call is attached // to a connection service, but in most cases will remain the same. if (call == null) { call = new Call(getNextCallId(), mContext, this, mLock, mConnectionServiceRepository, mContactsAsyncHelper, mCallerInfoAsyncQueryFactory, mPhoneNumberUtilsAdapter, handle, null /* gatewayInfo */, null /* connectionManagerPhoneAccount */, null /* phoneAccountHandle */, Call.CALL_DIRECTION_OUTGOING /* callDirection */, false /* forceAttachToExistingConnection */, false /* isConference */ ); if ((extras != null) && extras.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false)) { //Reset PostDialDigits with empty string for ConfURI call. call.setPostDialDigits(""); } call.initAnalytics(); call.setInitiatingUser(initiatingUser); isReusedCall = false; } // Set the video state on the call early so that when it is added to the InCall UI the UI // knows to configure itself as a video call immediately. if (extras != null) { int videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY); // If this is an emergency video call, we need to check if the phone account supports // emergency video calling. // Also, ensure we don't try to place an outgoing call with video if video is not // supported. if (VideoProfile.isVideo(videoState)) { PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser); if (call.isEmergencyCall() && account != null && !account.hasCapabilities(PhoneAccount.CAPABILITY_EMERGENCY_VIDEO_CALLING)) { // Phone account doesn't support emergency video calling, so fallback to // audio-only now to prevent the InCall UI from setting up video surfaces // needlessly. Log.i(this, "startOutgoingCall - emergency video calls not supported; " + "falling back to audio-only"); videoState = VideoProfile.STATE_AUDIO_ONLY; } else if (account != null && !account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) { // Phone account doesn't support video calling, so fallback to audio-only. Log.i(this, "startOutgoingCall - video calls not supported; fallback to " + "audio-only."); videoState = VideoProfile.STATE_AUDIO_ONLY; } } call.setVideoState(videoState); } boolean isAddParticipant = ((extras != null) && (extras.getBoolean( TelephonyProperties.ADD_PARTICIPANT_KEY, false))); boolean isSkipSchemaOrConfUri = ((extras != null) && (extras.getBoolean( TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false) || extras.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false))); if (isAddParticipant) { String number = handle.getSchemeSpecificPart(); if (!isSkipSchemaOrConfUri) { number = PhoneNumberUtils.stripSeparators(number); } addParticipant(number); mInCallController.bringToForeground(false); return null; } // Force tel scheme for ims conf uri/skip schema calls to avoid selection of sip accounts String scheme = (isSkipSchemaOrConfUri? PhoneAccount.SCHEME_TEL: handle.getScheme()); Log.d(this, "startOutgoingCall :: isAddParticipant=" + isAddParticipant + " isSkipSchemaOrConfUri=" + isSkipSchemaOrConfUri + " scheme=" + scheme); List accounts = constructPossiblePhoneAccounts(handle, initiatingUser, scheme); Log.v(this, "startOutgoingCall found accounts = " + accounts); // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call // as if a phoneAccount was not specified (does the default behavior instead). // Note: We will not attempt to dial with a requested phoneAccount if it is disabled. if (phoneAccountHandle != null) { if (!accounts.contains(phoneAccountHandle)) { phoneAccountHandle = null; } } if (phoneAccountHandle == null && accounts.size() > 0) { // No preset account, check if default exists that supports the URI scheme for the // handle and verify it can be used. if(accounts.size() > 1) { PhoneAccountHandle defaultPhoneAccountHandle = mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(scheme, initiatingUser); if (defaultPhoneAccountHandle != null && accounts.contains(defaultPhoneAccountHandle)) { phoneAccountHandle = defaultPhoneAccountHandle; } } else { // Use the only PhoneAccount that is available phoneAccountHandle = accounts.get(0); } } call.setTargetPhoneAccount(phoneAccountHandle); boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle); // Do not support any more live calls. Our options are to move a call to hold, disconnect // a call, or cancel this call altogether. If a call is being reused, then it has already // passed the makeRoomForOutgoingCall check once and will fail the second time due to the // call transitioning into the CONNECTING state. if (!isPotentialInCallMMICode && (!isReusedCall && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) { // just cancel at this point. Log.i(this, "No remaining room for outgoing call: %s", call); if (mCalls.contains(call)) { // This call can already exist if it is a reused call, // See {@link #reuseOutgoingCall}. call.disconnect(); } return null; } boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 && !call.isEmergencyCall(); if (needsAccountSelection) { // This is the state where the user is expected to select an account call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection"); // Create our own instance to modify (since extras may be Bundle.EMPTY) extras = new Bundle(extras); extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS, accounts); } else { call.setState( CallState.CONNECTING, phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString()); } setIntentExtrasAndStartTime(call, extras); // Do not add the call if it is a potential MMI code. if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { call.addListener(this); // If call is Emergency type and marked it as Pending, call would not be added // in mCalls here. It will be handled when the current active call (mDisconnectingCall) // is disconnected successfully. } else if (!mCalls.contains(call) && mPendingMOEmerCall == null) { // We check if mCalls already contains the call because we could potentially be reusing // a call which was previously added (See {@link #reuseOutgoingCall}). addCall(call); } return call; }   上篇文章已经分析过了,重点创建一个Call对象,但是却没有分析该函数的尾部有个addCall操作,addCall之后会有什么操作? 2 addCall private void addCall(Call call) { Trace.beginSection("addCall"); Log.v(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 (Log.SYSTRACE_DEBUG) { Trace.beginSection(listener.getClass().toString() + " addCall"); } listener.onCallAdded(call); if (Log.SYSTRACE_DEBUG) { Trace.endSection(); } } Trace.endSection(); }   该处是观察者模式,会通知到观察者listener.onCallAdded(call),其中我们看看都有哪些对象注册了监听. mListeners.add(mInCallWakeLockController); mListeners.add(statusBarNotifier); mListeners.add(mCallLogManager); mListeners.add(mPhoneStateBroadcaster); mListeners.add(mInCallController); mListeners.add(mCallAudioManager); mListeners.add(missedCallNotifier); mListeners.add(mHeadsetMediaButton); mListeners.add(mProximitySensorManager); mListeners.add(mViceNotificationImpl);   可以看出有通话记录相关的类,状态栏相关的类,耳机相关的类,距离传感器相关的类,其中重点是InCallController,看见Controller大家应该明白,InCallController是处理逻辑的关键类. 3.InCallController中处理 @Override public void onCallAdded(Call call) { if (!isBoundToServices()) { bindToServices(call); } else { adjustServiceBindingsForEmergency(); Log.i(this, "onCallAdded: %s", call); // Track the call if we don't already know about it. addCall(call); List componentsUpdated = new ArrayList<>(); for (Map.Entry entry : mInCallServices.entrySet()) { InCallServiceInfo info = entry.getKey(); if (call.isExternalCall() && !info.isExternalCallsSupported()) { continue; } componentsUpdated.add(info.getComponentName()); IInCallService inCallService = entry.getValue(); ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), info.isExternalCallsSupported()); try { inCallService.addCall(parcelableCall); } catch (RemoteException ignored) { } } Log.i(this, "Call added to components: %s", componentsUpdated); } }   该处会判断如果没有连接到服务就去绑定服务,我们看看去绑定什么服务: public void bindToServices(Call call) { 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()); mInCallServiceConnection.connect(call); List nonUIInCallComponents = getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI); List nonUIInCalls = new LinkedList<>(); for (InCallServiceInfo serviceInfo : nonUIInCallComponents) { nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo)); } mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); mNonUIInCallServiceConnections.connect(call); }   该处实际会调用InCallServiceBindingConnection的connect方法 public boolean connect(Call call) { if (mIsConnected) { Log.event(call, Log.Events.INFO, "Already connected, ignoring request."); return true; } Intent intent = new Intent(InCallService.SERVICE_INTERFACE); intent.setComponent(mInCallServiceInfo.getComponentName()); if (call != null && !call.isIncoming() && !call.isExternalCall()){ intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, call.getIntentExtras()); intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, call.getTargetPhoneAccount()); } Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent); mIsConnected = true; if (!mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | Context.BIND_ABOVE_CLIENT, UserHandle.CURRENT)) { Log.w(this, "Failed to connect."); mIsConnected = false; } if (call != null && mIsConnected) { call.getAnalytics().addInCallService( mInCallServiceInfo.getComponentName().flattenToShortString(), mInCallServiceInfo.getType()); } return mIsConnected; }    public static final String SERVICE_INTERFACE = "android.telecom.InCallService";   会去绑定action为android.telecom.InCallService的service,系统只有一个服务是接收该action的,就是InCallServiceImpl,路径为packages/apps/Dialer/InCallUI/src/com/android/incallui/InCallServiceImpl.java,该类继承自InCallService.   在InCallController中onCallAdded的bindToServices分析完了,绑定的是InCallServiceImpl,然后会调用inCallService.addCall(parcelableCall),那我们就知道实际走的是InCallServiceImpl的addCall,同时注意,该处已经由Telecom进程走到了Incall进程中,所以InCallServiceImpl是Telecom和Incall交互的媒介(其实有点不严谨,因为Telecom设置了android:process="system",所以运行在系统进程里面,为了更清晰,所以描述为Telecom进程). 4.InCallServiceImpl中处理,调用addCall,实际调用的是InCallServiceImpl的onCallAdded,中间饶了一下,有兴趣读者可以自己分析. public void onCallAdded(Call call) { InCallPresenter.getInstance().onCallAdded(call); } 5.InCallPresenter的onCallAdded: public void onCallAdded(final android.telecom.Call call) { if (shouldAttemptBlocking(call)) { maybeBlockCall(call); } else { if (call.getDetails() .hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) { mExternalCallList.onCallAdded(call); } else { mCallList.onCallAdded(call); } } // Since a call has been added we are no longer waiting for Telecom to send us a call. setBoundAndWaitingForOutgoingCall(false, null); call.registerCallback(mCallCallback); } 6.CallList的onCallAdded: public void onCallAdded(final android.telecom.Call telecomCall) { Trace.beginSection("onCallAdded"); final Call call = new Call(telecomCall); Log.d(this, "onCallAdded: callState=" + call.getState()); if (call.getState() == Call.State.INCOMING || call.getState() == Call.State.CALL_WAITING) { onIncoming(call, call.getCannedSmsResponses()); } else { onUpdate(call); } call.logCallInitiationType(); Trace.endSection(); }   我们不是来电,是去电,所以走的是onUpdate public void onUpdate(Call call) { Trace.beginSection("onUpdate"); PhoneAccountHandle ph = call.getAccountHandle(); Log.d(this, "onUpdate - " + call + " ph:" + ph); try { if (call.mIsActiveSub && ph != null) { int sub = Integer.parseInt(ph.getId()); Log.d(this, "onUpdate - sub:" + sub + " mSubId:" + mSubId); if(sub != mSubId) { setActiveSubId(sub); } } } catch (NumberFormatException e) { Log.w(this,"Sub Id is not a number " + e); } onUpdateCall(call); notifyGenericListeners(); Trace.endSection(); }   然后调用notifyGenericListeners private void notifyGenericListeners() { for (Listener listener : mListeners) { listener.onCallListChange(this); } } 7.InCallPresenter的onCallListChange public void onCallListChange(CallList callList) { if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null && mInCallActivity.getCallCardFragment().isAnimating()) { mAwaitingCallListUpdate = true; return; } if (callList == null) { return; } mAwaitingCallListUpdate = false; InCallState newState = getPotentialStateFromCallList(callList); InCallState oldState = mInCallState; Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState); newState = startOrFinishUi(newState); Log.d(this, "onCallListChange newState changed to " + newState); // Set the new state before announcing it to the world Log.i(this, "Phone switching state: " + oldState + " -> " + newState); mInCallState = newState; // notify listeners of new state for (InCallStateListener listener : mListeners) { Log.d(this, "Notify " + listener + " of state " + mInCallState.toString()); listener.onStateChange(oldState, mInCallState, callList); } if (isActivityStarted()) { final boolean hasCall = callList.getActiveOrBackgroundCall() != null || callList.getOutgoingCall() != null; mInCallActivity.dismissKeyguard(hasCall); } if (CallList.getInstance().isDsdaEnabled() && (mInCallActivity != null)) { mInCallActivity.updateDsdaTab(); } }   看见了没startOrFinishUi,该句最终会拉起IncallActivity.