Android7.0 Phone應用源碼分析(三) phone拒接流程分析

来源:http://www.cnblogs.com/lance2016/archive/2017/02/12/6391096.html
-Advertisement-
Play Games

Android7.0 phone拒接流程分析--- 本文為原創文章,轉載請註明出處,http://www.cnblogs.com/lance2016/p/6391096.html ...


接上篇博文:Android7.0 Phone應用源碼分析(二) phone來電流程分析

今天我們再來分析下Android7.0 的phone的拒接流程

incoming

下麵先來看一下拒接電話流程時序圖

步驟1:滑動按鈕到拒接圖標,會調用到AnswerFragment的onDecline方法

com.android.incallui.AnswerFragment
public void onDecline(Context context) {
        getPresenter().onDecline(context);
 }

最後是調用到AnswerPresenteronDecline方法

com.android.incallui.AnswerPresenter
 public void onDecline(Context context) {
        Log.d(this, "onDecline " + mCallId);
        if (mCall.getSessionModificationState()
                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
            InCallPresenter.getInstance().declineUpgradeRequest(context);
        } else {
            TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null);
        }
 }

步驟2:進入TelecomAdapter的rejectCall方法

com.android.incallui.TelecomAdapter
void rejectCall(String callId, boolean rejectWithMessage, String message) {
        android.telecom.Call call = getTelecomCallById(callId);
        if (call != null) {
            call.reject(rejectWithMessage, message);
        } else {
            Log.e(this, "error rejectCall, call not in call list: " + callId);
        }
}

TelecomAdapter是incallui與telecom通信的代理類,這裡通過callid取出對應的Call對象(android.telecom.Call)

步驟3:調用到framework里Call的reject方法

android.telecom.Call
public void reject(boolean rejectWithMessage, String textMessage) {
        mInCallAdapter.rejectCall(mTelecomCallId, rejectWithMessage, textMessage);
 }

這裡mInCallAdapter是android.telecom.InCallAdapter類,是在Call對象創建的時候由外部傳入的參數

在telecom綁定InCallService服務的時候,會傳遞一個AIDL介面對象,InCallService會生成InCallAdapter對象來保存這個介面對象

步驟4:InCallAdapter的rejectCall方法

android.telecom.InCallAdapter
public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
        try {
            mAdapter.rejectCall(callId, rejectWithMessage, textMessage);
        } catch (RemoteException e) {
        }
 }

mAdapter就是incallui與telecom通信的AIDL介面

步驟5:跨進程調用進入telecom進程,該AIDL介面具體實現類是InCallAdapter,雖然類名一樣但是不同的包名,這裡需要註意一下

com.android.server.telecom.InCallAdapter
 public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
        try {
            Log.startSession("ICA.rC", mOwnerComponentName);
            long token = Binder.clearCallingIdentity();
            try {
                synchronized (mLock) {
                    Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage);
                    Call call = mCallIdMapper.getCall(callId);
                    if (call != null) {
                        mCallsManager.rejectCall(call, rejectWithMessage, textMessage);
                    } else {
                        Log.w(this, "setRingback, unknown call id: %s", callId);
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        } finally {
            Log.endSession();
        }
 }

這裡同樣是根據callid取出對應Call(com.android.server.telecom.Call),最後調用CallsManager的rejectCall方法傳入call

步驟6:CallsManager的rejectCall方法

com.android.server.telecom.CallsManager
public void rejectCall(Call call, boolean rejectWithMessage, String textMessage) {
        if (!mCalls.contains(call)) {
            Log.i(this, "Request to reject a non-existent call %s", call);
        } else {
            for (CallsManagerListener listener : mListeners) {
                listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
            }
            call.reject(rejectWithMessage, textMessage);
        }
 }

這裡先通知觀察者來電拒接事件,比如CallAudioManager對該事件感興趣,它的處理是停止播放來電鈴聲和來電等待聲

com.android.server.telecom.CallAudioManager
 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
        maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
}
private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
        // Check to see if the call being answered/rejected is the only ringing call, since this
        // will be called before the connection service acknowledges the state change.
        if (mRingingCalls.size() == 0 ||
                (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
            mRinger.stopRinging();
            mRinger.stopCallWaiting();
        }
 }

最後再調用前面傳進來的call對象的reject方法

步驟7:Call的reject方法

com.android.server.telecom.Call
public void reject(boolean rejectWithMessage, String textMessage) {
        Preconditions.checkNotNull(mConnectionService);

        // Check to verify that the call is still in the ringing state. A call can change states
        // between the time the user hits 'reject' and Telecomm receives the command.
        if (isRinging("reject")) {
            // Ensure video state history tracks video state at time of rejection.
            mVideoStateHistory |= mVideoState;

            mConnectionService.reject(this, rejectWithMessage, textMessage);
            Log.event(this, Log.Events.REQUEST_REJECT);
        }
}

這裡的mConnectionService是ConnectionServiceWrapper類,是telecom與telephony通信的代理類

步驟8:ConnectionServiceWrapper的reject方法

com.android.server.telecom.ConnectionServiceWrapper
void reject(Call call, boolean rejectWithMessage, String message) {
        final String callId = mCallIdMapper.getCallId(call);
        if (callId != null && isServiceValid("reject")) {
            try {
                logOutgoing("reject %s", callId);

                if (rejectWithMessage && call.can(
                        Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
                    mServiceInterface.rejectWithMessage(callId, message);
                } else {
                    mServiceInterface.reject(callId);
                }
            } catch (RemoteException e) {
            }
        }
 }

這裡mServiceInterface就是telephony提供給telecom調用的AIDL介面

步驟9:跨進程調用進入telephony進程,telephony進程實際服務類是TelephonyConnectionService繼承於ConnectionService類在manifest聲明如下:

<service
         android:singleUser="true"
         android:name="com.android.services.telephony.TelephonyConnectionService"
         android:label="@string/pstn_connection_service_label"
         android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
          <intent-filter>
              <action android:name="android.telecom.ConnectionService" />
          </intent-filter>
</service>

而AIDL介面具體實現是其父類ConnectionService的mBinder成員變數

android.telecom.ConnectionService
 private final IBinder mBinder = new IConnectionService.Stub() {

        @Override
        public void reject(String callId) {
            mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
        }
}

步驟10~13:發送MSG_REJECT消息到隊列里處理

private void reject(String callId) {
        Log.d(this, "reject %s", callId);
        findConnectionForAction(callId, "reject").onReject();
 }

 private Connection findConnectionForAction(String callId, String action) {
        if (mConnectionById.containsKey(callId)) {
            return mConnectionById.get(callId);
        }
        Log.w(this, "%s - Cannot find Connection %s", action, callId);
        return getNullConnection();
}

根據callid找到對應的connection對象(android.telecom.Connection),調用onReject方法

步驟14:TelephonyConnection繼承於connection

com.android.services.telephony.TelephonyConnection
public void onReject() {
        Log.v(this, "onReject");
        if (isValidRingingCall()) {
            hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
        }
        super.onReject();
}
protected void hangup(int telephonyDisconnectCode) {
        if (mOriginalConnection != null) {
            try {
                // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
                // connection.hangup(). Without this change, the party originating the call will not
                // get sent to voicemail if the user opts to reject the call.
                if (isValidRingingCall()) {
                    Call call = getCall();
                    if (call != null) {
                        call.hangup();
                    } else {
                        Log.w(this, "Attempting to hangup a connection without backing call.");
                    }
                } else {
                    // We still prefer to call connection.hangup() for non-ringing calls in order
                    // to support hanging-up specific calls within a conference call. If we invoked
                    // call.hangup() while in a conference, we would end up hanging up the entire
                    // conference call instead of the specific connection.
                    mOriginalConnection.hangup();
                }
            } catch (CallStateException e) {
                Log.e(this, e, "Call to Connection.hangup failed with exception");
            }
        }
 }

步驟15,16:這獲取mOriginalConnection的call(com.android.internal.telephony.Call)對象,並調用hangup方法

protected Call getCall() {
        if (mOriginalConnection != null) {
            return mOriginalConnection.getCall();
        }
        return null;
 }

Call是抽象類,具體子類是GsmCdmaCall

com.android.internal.telephony.GsmCdmaCall
public void hangup() throws CallStateException {
        mOwner.hangup(this);
 }

mOwner是GsmCdmaCallTracker對象

步驟17:GsmCdmaCallTracker的hangup方法

com.android.internal.telephony.GsmCdmaCallTracker
public void hangup(GsmCdmaCall call) throws CallStateException {
        if (call.getConnections().size() == 0) {
            throw new CallStateException("no connections in call");
        }

        if (call == mRingingCall) {
            if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background");
            mCi.hangupWaitingOrBackground(obtainCompleteMessage());
        } else if (call == mForegroundCall) {
            if (call.isDialingOrAlerting()) {
                if (Phone.DEBUG_PHONE) {
                    log("(foregnd) hangup dialing or alerting...");
                }
                hangup((GsmCdmaConnection)(call.getConnections().get(0)));
            } else if (isPhoneTypeGsm()
                    && mRingingCall.isRinging()) {
                // Do not auto-answer ringing on CHUP, instead just end active calls
                log("hangup all conns in active/background call, without affecting ringing call");
                hangupAllConnections(call);
            } else {
                hangupForegroundResumeBackground();
            }
        } else if (call == mBackgroundCall) {
            if (mRingingCall.isRinging()) {
                if (Phone.DEBUG_PHONE) {
                    log("hangup all conns in background call");
                }
                hangupAllConnections(call);
            } else {
                hangupWaitingOrBackground();
            }
        } else {
            throw new RuntimeException ("GsmCdmaCall " + call +
                    "does not belong to GsmCdmaCallTracker " + this);
        }

        call.onHangupLocal();
        mPhone.notifyPreciseCallStateChanged();
 }

由於是ringcall,這裡調用mCi.hangupWaitingOrBackground(obtainCompleteMessage());

mCi是CommandsInterface即RILJ介面,包裝了一個EVENT_OPERATION_COMPLETE回調消息,發送給RIL

步驟18:RIL的hangupWaitingOrBackground方法

com.android.internal.telephony.RIL
hangupWaitingOrBackground (Message result) {
        RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND,
                                        result);

        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));

        mEventLog.writeRilHangup(rr.mSerial, RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, -1);

        send(rr);
 }

給RIL層發送RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND消息

步驟19:mPhone.notifyPreciseCallStateChanged通知Phone狀態監聽事件

步驟20~24:收到RIL層的回應消息並處理,最後發送回調消息EVENT_OPERATION_COMPLETE給GsmCdmaCallTracker

步驟25:GsmCdmaCallTracker處理回調消息EVENT_OPERATION_COMPLETE

com.android.internal.telephony.GsmCdmaCallTracker
 private void operationComplete() {
        mPendingOperations--;

        if (DBG_POLL) log("operationComplete: pendingOperations=" +
                mPendingOperations + ", needsPoll=" + mNeedsPoll);

        if (mPendingOperations == 0 && mNeedsPoll) {
            mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
            mCi.getCurrentCalls(mLastRelevantPoll);
        } else if (mPendingOperations < 0) {
            // this should never happen
            Rlog.e(LOG_TAG,"GsmCdmaCallTracker.pendingOperations < 0");
            mPendingOperations = 0;
        }
 }

這裡再次向RIL發送消息主動獲取當前Call狀態,包裝的回調消息為EVENT_POLL_CALLS_RESULT

步驟26~32:RIL返回消息,GsmCdmaCallTracker接收EVENT_POLL_CALLS_RESULT消息並處理

protected synchronized void handlePollCalls(AsyncResult ar) {
     ...................

     for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) {
            GsmCdmaConnection conn = mDroppedDuringPoll.get(i);
            //CDMA
            boolean wasDisconnected = false;

            if (conn.isIncoming() && conn.getConnectTime() == 0) {
                // Missed or rejected call
                int cause;
                if (conn.mCause == DisconnectCause.LOCAL) {
                    cause = DisconnectCause.INCOMING_REJECTED;
                } else {
                    cause = DisconnectCause.INCOMING_MISSED;
                }

                if (Phone.DEBUG_PHONE) {
                    log("missed/rejected call, conn.cause=" + conn.mCause);
                    log("setting cause to " + cause);
                }
                mDroppedDuringPoll.remove(i);
                hasAnyCallDisconnected |= conn.onDisconnect(cause);
                wasDisconnected = true;
            } else if (conn.mCause == DisconnectCause.LOCAL
                    || conn.mCause == DisconnectCause.INVALID_NUMBER) {
                mDroppedDuringPoll.remove(i);
                hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause);
                wasDisconnected = true;
            }

            if (!isPhoneTypeGsm() && wasDisconnected && unknownConnectionAppeared
                    && conn == newUnknownConnectionCdma) {
                unknownConnectionAppeared = false;
                newUnknownConnectionCdma = null;
            }
        
     ...................
     ...................

       updatePhoneState();

        if (unknownConnectionAppeared) {
            if (isPhoneTypeGsm()) {
                for (Connection c : newUnknownConnectionsGsm) {
                    log("Notify unknown for " + c);
                    mPhone.notifyUnknownConnection(c);
                }
            } else {
                mPhone.notifyUnknownConnection(newUnknownConnectionCdma);
            }
        }

        if (hasNonHangupStateChanged || newRinging != null || hasAnyCallDisconnected) {
            mPhone.notifyPreciseCallStateChanged();
        }


}

這裡設置DisconnectCause.INCOMING_REJECTED為連接斷開的cause並調用GsmCdmaConnection的onDisconnect方法

步驟33:GsmCdmaConnection的onDisconnect方法

com.android.internal.telephony.GsmCdmaConnection
 public boolean onDisconnect(int cause) {
        boolean changed = false;

        mCause = cause;

        if (!mDisconnected) {
            doDisconnect();

            if (DBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);

            mOwner.getPhone().notifyDisconnect(this);

            if (mParent != null) {
                changed = mParent.connectionDisconnected(this);
            }

            mOrigConnection = null;
        }
        clearPostDialListeners();
        releaseWakeLock();
        return changed;
  }

doDisconnect方法設置斷開時間以及通話時長

private void  doDisconnect() {
        mIndex = -1;
        mDisconnectTime = System.currentTimeMillis();
        mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
        mDisconnected = true;
        clearPostDialListeners();
}

最後通知註冊者斷開事件mOwner.getPhone().notifyDisconnect(this);

步驟34,36:通知phone狀態變化事件給相關監聽者

步驟35:GsmCdmaPhone通知通話斷開事件

com.android.internal.telephony.GsmCdmaPhone
 public void notifyDisconnect(Connection cn) {
        mDisconnectRegistrants.notifyResult(cn);

        mNotifier.notifyDisconnectCause(cn.getDisconnectCause(), cn.getPreciseDisconnectCause());
}

步驟37~40:TelephonyConnection註冊了斷開事件監聽,接收並處理斷開消息

com.android.services.telephony.TelephonyConnection
void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
        Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
 ......
 getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
 ......
}
void updateState() {
        if (mOriginalConnection == null) {
            return;
        }

        updateStateInternal();
        updateStatusHints();
        updateConnectionCapabilities();
        updateConnectionProperties();
        updateAddress();
        updateMultiparty();
 }
void updateStateInternal() {
        if (mOriginalConnection == null) {
            return;
        }
        Call.State newState;
        // If the state is overridden and the state of the original connection hasn't changed since,
        // then we continue in the overridden state, else we go to the original connection's state.
        if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) {
            newState = mConnectionOverriddenState;
        } else {
            newState = mOriginalConnection.getState();
        }
        Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this);

        if (mConnectionState != newState) {
            mConnectionState = newState;
            switch (newState) {
                case IDLE:
                    break;
                case ACTIVE:
                    setActiveInternal();
                    break;
                case HOLDING:
                    setOnHold();
                    break;
                case DIALING:
                case ALERTING:
                    setDialing();
                    break;
                case INCOMING:
                case WAITING:
                    setRinging();
                    break;
                case DISCONNECTED:
                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
                            mOriginalConnection.getDisconnectCause(),
                            mOriginalConnection.getVendorDisconnectCause()));
                    close();
                    break;
                case DISCONNECTING:
                    break;
            }
        }
}

通過DisconnectCauseUtil的toTelecomDisconnectCause方法生成DisconnectCause(android.telecom.DisconnectCause)對象

包含code, label, description, reason,toneToPlay信息

步驟41,42:通知外部監聽者斷開事件mNotifier.notifyDisconnectCause

步驟43:調用父類Connection的setDisconnected方法

public final void setDisconnected(DisconnectCause disconnectCause) {
        checkImmutable();
        mDisconnectCause = disconnectCause;
        setState(STATE_DISCONNECTED);
        Log.d(this, "Disconnected with cause %s", disconnectCause);
        for (Listener l : mListeners) {
            l.onDisconnected(this, disconnectCause);
        }
    }

回調通知觀察者ConnectionService註冊了該事件,mConnectionListener接收處理

步驟44:mConnectionListener處理onDisconnected事件

android.telecom.ConnectionService
private final Connection.Listener mConnectionListener = new Connection.Listener() {
 ......
 @Override
        public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
            String id = mIdByConnection.get(c);
            Log.d(this, "Adapter set disconnected %s", disconnectCause);
            mAdapter.setDisconnected(id, disconnectCause);
        }

}

根據connection對象取出對應的callid

步驟45:TelephonyConnection的updateAddress方法更新connection信息

步驟46:ConnectionServiceAdapter的setDisconnected方法

android.telecom.ConnectionServiceAdapter
 void setDisconnected(String callId, DisconnectCause disconnectCause) {
        for (IConnectionServiceAdapter adapter : mAdapters) {
            try {
                adapter.setDisconnected(callId, disconnectCause);
            } catch (RemoteException e) {
            }
        }
}

telecom在綁定TelephonyConnectionService的時候,會設置AIDL回調介面對象給telephony即ConnectionServiceWrapper的Adapter成員變數

步驟47:跨進程調用到telecom進程,ConnectionServiceWrapper的Adapter處理setDisconnected

com.android.server.telecom.ConnectionServiceWrapper
 private final class Adapter extends IConnectionServiceAdapter.Stub {
......
  @Override
        public void setDisconnected(String callId, DisconnectCause disconnectCause) {
            Log.startSession("CSW.sD");
            long token = Binder.clearCallingIdentity();
            try {
                synchronized (mLock) {
                    logIncoming("setDisconnected %s %s", callId, disconnectCause);
                    Call call = mCallIdMapper.getCall(callId);
                    Log.d(this, "disconnect call %s %s", disconnectCause, call);
                    if (call != null) {
                        mCallsManager.markCallAsDisconnected(call, disconnectCause);
                    } else {
                        // Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(token);
                Log.endSession();
            }
        }
......
}

根據callid取出Call(com.android.server.telecom.Call)對象,給CallsManager傳遞Call和disconnectCause

步驟48,49,50:CallsManager的markCallAsDisconnected方法

com.android.server.telecom.CallsManager
void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
        call.setDisconnectCause(disconnectCause);
        setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
}

給Call設置disconnectCause,同時設置callstate

private void setCallState(Call call, int newState, String tag) {
        if (call == null) {
            return;
        }
        int oldState = call.getState();
        Log.i(this, "setCallState %s -> %s, call: %s", CallState.toString(oldState),
                CallState.toString(newState), call);
        if (newState != oldState) {
            // Unfortunately, in the telephony world the radio is king. So if the call notifies
            // us that the call is in a particular state, we allow it even if it doesn't make
            // sense (e.g., STATE_ACTIVE -> STATE_RINGING).
            // TODO: Consider putting a stop to the above and turning CallState
            // into a well-defined state machine.
            // TODO: Define expected state transitions here, and log when an
            // unexpected transition occurs.
            call.setState(newState, tag);
            maybeShowErrorDialogOnDisconnect(call);

            Trace.beginSection("onCallStateChanged");
            // Only broadcast state change for calls that are being tracked.
            if (mCalls.contains(call)) {
                updateCallsManagerState();
                for (CallsManagerListener listener : mListeners) {
                    if (Log.SYSTRACE_DEBUG) {
                        Trace.beginSection(listener.getClass().toString() + " onCallStateChanged");
                    }
                    listener.onCallStateChanged(call, oldState, newState);
                    if (Log.SYSTRACE_DEBUG) {
                        Trace.endSection();
                    }
                }
            }
            Trace.endSection();
        }
}

最後回調onCallStateChanged方法通知監聽者,這裡監聽call狀態變化的對象有很多,我們看下InCallController的處理

步驟51,52:InCallController的onCallStateChanged方法

com.android.server.telecom.InCallController
 @Override
    public void onCallStateChanged(Call call, int oldState, int newState) {
        updateCall(call);
    }
private void updateCall(Call call, boolean videoProviderChanged) {
        if (!mInCallServices.isEmpty()) {
            ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
                    call,
                    videoProviderChanged /* includeVideoProvider */,
                    mCallsManager.getPhoneAccountRegistrar());
            Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall);
            List<ComponentName> componentsUpdated = new ArrayList<>();
            for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
                ComponentName componentName = entry.getKey();
                IInCallService inCallService = entry.getValue();
                componentsUpdated.add(componentName);
                try {
                    inCallService.updateCall(parcelableCall);
                } catch (RemoteException ignored) {
                }
            }
            Log.i(this, "Components updated: %s", componentsUpdated);
        }
    }
}

根據call信息生成ParcelableCall對象,給incallservice傳遞ParcelableCall

步驟53,54:InCallService的updateCall方法

android.telecom.InCallService
 @Override
        public void updateCall(ParcelableCall call) {
            mHandler.obtainMessage(MSG_UPDATE_CALL, call).sendToTarget();
 }
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
 ......
                case MSG_UPDATE_CALL:
                    mPhone.internalUpdateCall((ParcelableCall) msg.obj);
                    break;

}

步驟55:Phone的internalUpdateCall方法

android.telecom.Phone
 final void internalUpdateCall(ParcelableCall parcelableCall) {
         Call call = mCallByTelecomCallId.get(parcelableCall.getId());
         if (call != null) {
             checkCallTree(parcelableCall);
             call.internalUpdate(parcelableCall, mCallByTelecomCallId);
         }
}

這裡的Phone對象只是一個管理類,保存call列表信息和與telecom通信的AIDL介面對象,通過callid取出Call(android.telecom.Call)對象

步驟56:Call的internalUpdate方法

android.telecom.Call
final void internalUpdate(ParcelableCall parcelableCall, Map<String, Call> callIdMap) {

        Details details = Details.createFromParcelableCall(parcelableCall);
      
        ......
        ......

        // Now we fire updates, ensuring that any client who listens to any of these notifications
        // gets the most up-to-date state.

        if (stateChanged) {
            fireStateChanged(mState);
        }
        if (detailsChanged) {
            fireDetailsChanged(mDetails);
        }
        if (cannedTextResponsesChanged) {
            fireCannedTextResponsesLoaded(mCannedTextResponses);
        }
        if (videoCallChanged) {
            fireVideoCallChanged(mVideoCallImpl);
        }
        if (parentChanged) {
            fireParentChanged(getParent());
        }
        if (childrenChanged) {
            fireChildrenChanged(getChildren());
        }

        // If we have transitioned to DISCONNECTED, that means we need to notify clients and
        // remove ourselves from the Phone. Note that we do this after completing all state updates
        // so a client can cleanly transition all their UI to the state appropriate for a
        // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list.
        if (mState == STATE_DISCONNECTED) {
            fireCallDestroyed();
        }


}

步驟57:轉化ParcelableCall 信息為Detail信息,判斷call狀態是否有變化,有則進入fireStateChanged

private void fireStateChanged(final int newState) {
        for (CallbackRecord<Callback> record : mCallbackRecords) {
            final Call call = this;
            final Callback callback = record.getCallback();
            record.getHandler().post(new Runnable() {
                @Override
                public void run() {
                    callback.onStateChanged(call, newState);
                }
            });
        }
}

步驟58:這裡遍歷Call(android.telecom.Call)對象里的回調監聽者

private final List<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArrayList<>();

也就是每次InCallPresenter添加Call(android.telecom.Call)時添加的註冊回調事件

com.android.incallui.InCallPresenter
public void onCallAdded(final android.telecom.Call call) {
        if (shouldAttemptBlocking(call)) {
            maybeBlockCall(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);
 }

這裡有兩個地方註冊了事件回調,一個是在CallList的onCallAdd方法里轉化Call(android.telecom.Call)創建了Call(com.android.incallui.Call)對象時註冊的

com.android.incallui.CallList
 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();
 }
com.android.incallui.Call
public Call(android.telecom.Call telecomCall) {
        mTelecomCall = telecomCall;
        mId = ID_PREFIX + Integer.toString(sIdCounter++);

        updateFromTelecomCall();

        mTelecomCall.registerCallback(mTelecomCallCallback);

        mTimeAddedMs = System.currentTimeMillis();
   }

還有就是InCallPresenter的成員變數mCallCallback的註冊

這裡onStateChange只有Call(com.android.incallui.Call)的成員變數mTelecomCallCallback有處理

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JavaScript 在瀏覽器中的性能成為開發者所面臨的最重要的可用性問題。而這個問題又因 JavaScript 的阻塞特性變的複雜,也就是說當瀏覽器在執行 JavaScript 代碼時,不能同時做其他任何事情。本文詳細介紹瞭如何正確的載入和執行 JavaScript 代碼,從而提高其在瀏覽器中的性... ...
  • 阻止預設事件 function stopDeFault(e){ if(e&&e.preventDefault){//非IE e.preventDefault(); }else{//IE window.event.returnValue=false; } } 阻止事件冒泡 function stopB ...
  • 一、前言: 當我們還在沉迷於ES5的時候,殊不知ES6早就已經發佈幾年了。時代在進步,WEB前端技術也在日新月異,是時候做些改變了! ECMAScript 6(ES6)的發展速度非常之快,但現代瀏覽器對ES6新特性支持度不高,所以要想在瀏覽器中直接使用ES6的新特性就得藉助別的工具來實現。 Babe ...
  • 單獨對接每個快遞公司的api會比較麻煩,一般都選擇第三方來對接 服務來源: 阿裡雲 付費(0.01元100次) 購買服務後,商家提供AppKey、AppSecret、AppCode 購買物流查詢介面api的服務,到官網上能找到各種程式語言對應的demo,稍微修改下就能正常使用.重溫下curl以及aj ...
  • [1]創建範圍 [2]簡單選擇 [3]複雜選擇 [4]操作範圍內容 [5]插入範圍內容 [6]摺疊範圍 [7]比較範圍 [8]複製範圍 [9]清理範圍 [10]IE相容 ...
  • 一、box-shadow box-shadow是css3中新增的屬性,用於增加邊框陰影,讓原有的元素變得更多樣性,它名下有四位小弟,老大控制水平方向偏移,老二控制垂直方向偏移,老三控制模糊度,最小的老四控制陰影顏色。 其中老大老二老三是一組三胞胎,都是像素(px)家族的,而老大老二又是同卵,控制的都 ...
  • 一、瀑布流是個啥? 瀑布流,是比較流行的一種網站頁面佈局,視覺表現為參差不齊的多欄佈局,隨著頁面滾動條向下滾動,這種佈局還會不斷載入數據塊並附加至當前尾部。 最早採用瀑布流佈局的網站是Pinterest,逐漸在國內流行開來,比如我們熟知的百度圖片的佈局,在“很久”以前,百度圖片還是需要一頁一頁的點擊 ...
  • 在iOS 8中,蘋果引入了UITableView的一項新功能--Self Sizing Cells,對於不少開發者來說這是新SDK中一項非常有用的新功能。在iOS 8之前,如果想在表視圖中展示可變高度的動態內容時,你需要手動計算行高,而Self Sizing Cells為展示動態內容提供了一個解決方... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...