Telephony Phone與system鏈接監聽關係(9.0源碼)

首先了解Phone  與 system是什麼 ,我在網上找了一張圖片先了解一下

設計圖:

  1.    Dialer  (dialer進程 )撥打電話的入口,來電不會經過Dialer。但是撥打電話的出口不光是Dialer,在聯繫人和短信裏也有撥打電話的出口。
  2.  InCallUI (dialer進程)   負責顯示通話界面的信息,來電信息。
  3. Telecomm(system_process和telecomm:ui進程)   處理Intent,發送廣播,設置call的狀態,audio狀態。 
  4. Telephony ( phone進程) 向下層傳遞撥號,註冊了很多廣播,申請很多權限,service data sms wap network等。
  5. telecomm(phone_process)  提供placeCall的接口,創建outgoingCall的connection,通知上層成功建立connection
  6. telephony (phone_process) 撥號也就是dial命令的下發,但是如果是Ims網絡就會有下面一步
  7. Vendor/ims(phone_process)   創建ImsConnection,ImsCall,撥號.

連接入口在system_process進程中的ConnectionServiceWrapper.java
system進程與phone進程之間連接流程圖

流程圖:

 

 

 

第一步

ConnectionServiceWrapper.java

system進入ConnectionServiceWrapper.java中的createConnection方法

步驟一

  public void createConnection(final Call call, final CreateConnectionResponse response) {//創建鏈接就是一個綁定服務的過程
        Log.d(this, "createConnection(%s) via %s.", call, getComponentName());
        BindCallback callback = new BindCallback() {// 鏈接創建成功後 onSuccess 會被調用
            @Override
            public void onSuccess() {
                String callId = mCallIdMapper.getCallId(call);
                mPendingResponses.put(callId, response);

                GatewayInfo gatewayInfo = call.getGatewayInfo();
                Bundle extras = call.getIntentExtras();
                if (gatewayInfo != null && gatewayInfo.getGatewayProviderPackageName() != null &&
                        gatewayInfo.getOriginalAddress() != null) {
                    extras = (Bundle) extras.clone();
                    extras.putString(
                            TelecomManager.GATEWAY_PROVIDER_PACKAGE,
                            gatewayInfo.getGatewayProviderPackageName());
                    extras.putParcelable(
                            TelecomManager.GATEWAY_ORIGINAL_ADDRESS,
                            gatewayInfo.getOriginalAddress());
                }

                if (call.isIncoming() && mCallsManager.getEmergencyCallHelper()
                        .getLastEmergencyCallTimeMillis() > 0) {
                  // Add the last emergency call time to the connection request for incoming calls
                  if (extras == call.getIntentExtras()) {
                    extras = (Bundle) extras.clone();
                  }
                  extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
                      mCallsManager.getEmergencyCallHelper().getLastEmergencyCallTimeMillis());
                }

                // Call is incoming and added because we're handing over from another; tell CS
                // that its expected to handover.
                if (call.isIncoming() && call.getHandoverSourceCall() != null) {
                    extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
                    extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
                            call.getHandoverSourceCall().getTargetPhoneAccount());
                }

                Log.addEvent(call, LogUtils.Events.START_CONNECTION,
                        Log.piiHandle(call.getHandle()));

                ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
                        .setAccountHandle(call.getTargetPhoneAccount())
                        .setAddress(call.getHandle())
                        .setExtras(extras)
                        .setVideoState(call.getVideoState())
                        .setTelecomCallId(callId)
                        // For self-managed incoming calls, if there is another ongoing call Telecom
                        // is responsible for showing a UI to ask the user if they'd like to answer
                        // this new incoming call.
                        .setShouldShowIncomingCallUi(
                                !mCallsManager.shouldShowSystemIncomingCallUi(call))
                        .setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
                        .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
                        .build();

                try {

                    mServiceInterface.createConnection( // 通過遠程接口創建鏈接     利用遠程接口創建一個通話鏈接
                            call.getConnectionManagerPhoneAccount(),
                            callId,
                            connectionRequest,
                            call.shouldAttachToExistingConnection(),
                            call.isUnknown(),
                            Log.getExternalSession());

                } catch (RemoteException e) {
                    Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
                    mPendingResponses.remove(callId).handleCreateConnectionFailure(
                            new DisconnectCause(DisconnectCause.ERROR, e.toString()));
                }
            }

            @Override
            public void onFailure() {
                Log.e(this, new Exception(), "Failure to call %s", getComponentName());
                response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR));
            }
        };
        //綁定
        mBinder.bind(callback, call);

ServiceBinder.java

進入mBinder.bind中 ConnectionServiceWrapper是ServiceBinder的子類,而mBinder.bind方法是ServiceBinder裏面的內部類Binder2 中
步驟二

 void bind(BindCallback callback, Call call) {
            Log.d(ServiceBinder.this, "bind()");

            // Reset any abort request if we're asked to bind again.
            clearAbort();

            if (!mCallbacks.isEmpty()) {
                // Binding already in progress, append to the list of callbacks and bail out.
                mCallbacks.add(callback);
                return;
            }
            //在類裏面綁定成功後mBinder.bind(callback, call);在父類ServiceBinder類裏面mCallbacks.add(callback);吧回調用集合給裝載起來,然後進行便利數據
            //成功獲取到aidl接口後將其賦值mServiceInterface,
            mCallbacks.add(callback);
            if (mServiceConnection == null) {
                Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
                ServiceConnection connection = new ServiceBinderConnection(call);

                Log.addEvent(call, LogUtils.Events.BIND_CS, mComponentName);
                final int bindingFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
                final boolean isBound;
                if (mUserHandle != null) {// 進行綁定
                    isBound = mContext.bindServiceAsUser(serviceIntent, connection, bindingFlags,  //如果綁定成功調用connection方法  這裏綁定是綁定了framework/base/telepony
                            mUserHandle);
                } else {
                    isBound = mContext.bindService(serviceIntent, connection, bindingFlags);
                }
                if (!isBound) {
                    handleFailedConnection();
                    return;
                }
            } else {
                Log.d(ServiceBinder.this, "Service is already bound.");
                Preconditions.checkNotNull(mBinder);
                handleSuccessfulConnection();
            }
        }
    }

步驟三

mContext.bindServiceAsUser()傳入了一個connection對象代表,如果連接成功回調到connection對象中,connection繼承ServiceConnection,我們來看一下connection對象吧,

回調onServiceConnected()方法,返回一個bind
步驟四

 private final class ServiceBinderConnection implements ServiceConnection {
        /**
         * 綁定服務的初始調用。
         */
        private Call mCall;

        ServiceBinderConnection(Call call) {
            mCall = call;
        }

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder binder) {
            try {
                Log.startSession("SBC.oSC");
                synchronized (mLock) {
                    Log.i(this, "Service bound %s", componentName);

                    Log.addEvent(mCall, LogUtils.Events.CS_BOUND, componentName);
                    mCall = null;

                    //取消綁定請求被排隊,因此立即取消綁定。
                    if (mIsBindingAborted) {
                        clearAbort();
                        logServiceDisconnected("onServiceConnected");
                        mContext.unbindService(this);
                        handleFailedConnection();
                        return;
                    }
                    if (binder != null) {
                        mServiceDeathRecipient = new ServiceDeathRecipient(componentName);
                        try {

                            binder.linkToDeath(mServiceDeathRecipient, 0);
                            mServiceConnection = this;
                            // 設置 mServiceInterface
                            // 會觸發 ConnectionServiceWrapper.setServiceInterface  ==>  ConnectionServiceWrapper.addConnectionServiceAdapter
                            // 通過mServiceInterface,給綁定的服務,提供一個訪問自己的接口
                            setBinder(binder);// 觸發bind(BindCallback callback, Call call) 中 callback 的 onSuccess
                            handleSuccessfulConnection();  //回調進入子類
                            //整個綁定過程,只做2件事,一是給遠程服務提供訪問自己的接口,二是利用遠程接口創建一個通話鏈接。
                            // 這2件事都是跨進程進行的。遠程服務訪問自己的接口是
                        } catch (RemoteException e) {
                            Log.w(this, "onServiceConnected: %s died.");
                            if (mServiceDeathRecipient != null) {
                                mServiceDeathRecipient.binderDied();
                            }
                        }
                    }
                }
            } finally {
                Log.endSession();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
              失敗處理...
        }
    }
入口步驟五
setBinder() 回調到ServiceConnectionWrapper.java中setServiceInterface() 遠程服務提供訪問自己的接口
 private void setBinder(IBinder binder) {
        if (mBinder != binder) {
            if (binder == null) {
                removeServiceInterface();
                mBinder = null;
                for (Listener l : mListeners) {
                    l.onUnbind(this);
                }
            } else {
                mBinder = binder;
                setServiceInterface(binder);
            }
        }
    }

步驟六

setServiceInterace() 調用的是子類實現方法  ,他的子類是ConnectionServiceWrapper.java
ConnectionServiceWrapper.java

  @Override
    protected void setServiceInterface(IBinder binder) {
        mServiceInterface = IConnectionService.Stub.asInterface(binder);
        Log.v(this, "Adding Connection Service Adapter.");
        addConnectionServiceAdapter(mAdapter);//這個mAdapter  給遠程服務提供訪問自己的接口
    }

步驟七

 /** See {@link IConnectionService#addConnectionServiceAdapter}. */
    private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
        if (isServiceValid("addConnectionServiceAdapter")) {
            try {
                //在類裏面綁定成功後mBinder.bind(callback, call);在父類ServiceBinder類裏面mCallbacks.add(callback);吧回調用集合給裝載起來,然後進行便利數據
                //成功獲取到aidl接口後將其賦值mServiceInterface,
                logOutgoing("addConnectionServiceAdapter %s", adapter);
                mServiceInterface.addConnectionServiceAdapter(adapter, Log.getExternalSession());
            } catch (RemoteException e) {
            }
        }
    }

完成遠程服務提供訪問自己的接口

 

步驟八

回到ServiceBinder中子類對象中的connection對象裏面的onServiceConnected()方法

然後調用 handleSuccessfulConnection()

    private void handleSuccessfulConnection() {
        for (BindCallback callback : mCallbacks) {
            callback.onSuccess();
        }
        mCallbacks.clear();
    }

步驟九

回調到ConnectionServiceWrapper中Bindcallback中的onSuccess()方法中

  @Override
            public void onSuccess() {
                String callId = mCallIdMapper.getCallId(call);
                mPendingResponses.put(callId, response);

                GatewayInfo gatewayInfo = call.getGatewayInfo();
                Bundle extras = call.getIntentExtras();
                if (gatewayInfo != null && gatewayInfo.getGatewayProviderPackageName() != null &&
                        gatewayInfo.getOriginalAddress() != null) {
                    extras = (Bundle) extras.clone();
                    extras.putString(
                            TelecomManager.GATEWAY_PROVIDER_PACKAGE,
                            gatewayInfo.getGatewayProviderPackageName());
                    extras.putParcelable(
                            TelecomManager.GATEWAY_ORIGINAL_ADDRESS,
                            gatewayInfo.getOriginalAddress());
                }

                if (call.isIncoming() && mCallsManager.getEmergencyCallHelper()
                        .getLastEmergencyCallTimeMillis() > 0) {
                  // Add the last emergency call time to the connection request for incoming calls
                  if (extras == call.getIntentExtras()) {
                    extras = (Bundle) extras.clone();
                  }
                  extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
                      mCallsManager.getEmergencyCallHelper().getLastEmergencyCallTimeMillis());
                }

                // Call is incoming and added because we're handing over from another; tell CS
                // that its expected to handover.
                if (call.isIncoming() && call.getHandoverSourceCall() != null) {
                    extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
                    extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
                            call.getHandoverSourceCall().getTargetPhoneAccount());
                }

                Log.addEvent(call, LogUtils.Events.START_CONNECTION,
                        Log.piiHandle(call.getHandle()));

                ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
                        .setAccountHandle(call.getTargetPhoneAccount())
                        .setAddress(call.getHandle())
                        .setExtras(extras)
                        .setVideoState(call.getVideoState())
                        .setTelecomCallId(callId)
                        // For self-managed incoming calls, if there is another ongoing call Telecom
                        // is responsible for showing a UI to ask the user if they'd like to answer
                        // this new incoming call.
                        .setShouldShowIncomingCallUi(
                                !mCallsManager.shouldShowSystemIncomingCallUi(call))
                        .setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
                        .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
                        .build();

                try {

                    mServiceInterface.createConnection( // 通過遠程接口創建鏈接     利用遠程接口創建一個通話鏈接
                            call.getConnectionManagerPhoneAccount(),
                            callId,
                            connectionRequest,
                            call.shouldAttachToExistingConnection(),
                            call.isUnknown(),
                            Log.getExternalSession());

                } catch (RemoteException e) {
                    Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
                    mPendingResponses.remove(callId).handleCreateConnectionFailure(
                            new DisconnectCause(DisconnectCause.ERROR, e.toString()));
                }
            }

這個方法內部

步驟十

phone進程

ConnectionService.java

@Override
        public void createConnection(
                PhoneAccountHandle connectionManagerPhoneAccount,
                String id,
                ConnectionRequest request,
                boolean isIncoming,
                boolean isUnknown,
                Session.Info sessionInfo) {
            Log.startSession(sessionInfo, SESSION_CREATE_CONN);
            try {
                SomeArgs args = SomeArgs.obtain();
                args.arg1 = connectionManagerPhoneAccount;
                args.arg2 = id;
                args.arg3 = request;
                args.arg4 = Log.createSubsession();
                args.argi1 = isIncoming ? 1 : 0;
                args.argi2 = isUnknown ? 1 : 0;
                mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
            } finally {
                Log.endSession();
            }
        }

步驟十一

創建連接MSG_CREATE_CONNECTION

  private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
             case MSG_ADD_CONNECTION_SERVICE_ADAPTER: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try { // 添加 來自於 system 進程的接口
                        IConnectionServiceAdapter adapter = (IConnectionServiceAdapter) args.arg1;
                        Log.continueSession((Session) args.arg2,
                                SESSION_HANDLER + SESSION_ADD_CS_ADAPTER);
                        mAdapter.addAdapter(adapter);
                        onAdapterAttached();
                    } finally {
                        args.recycle();
                        Log.endSession();
                    }
                    break;
                }
                case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        Log.continueSession((Session) args.arg2,
                                SESSION_HANDLER + SESSION_REMOVE_CS_ADAPTER);
                        mAdapter.removeAdapter((IConnectionServiceAdapter) args.arg1); // 移除
                    } finally {
                        args.recycle();
                        Log.endSession();
                    }
                    break;
                }
                case MSG_CREATE_CONNECTION: { // 創建鏈接
                    SomeArgs args = (SomeArgs) msg.obj;
                    Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN);
                    try {
                        final PhoneAccountHandle connectionManagerPhoneAccount =
                                (PhoneAccountHandle) args.arg1;
                        final String id = (String) args.arg2;
                        final ConnectionRequest request = (ConnectionRequest) args.arg3;
                        final boolean isIncoming = args.argi1 == 1;
                        final boolean isUnknown = args.argi2 == 1;
                        if (!mAreAccountsInitialized) {
                            Log.d(this, "Enqueueing pre-init request %s", id);
                            mPreInitializationConnectionRequests.add(
                                    new android.telecom.Logging.Runnable(
                                            SESSION_HANDLER + SESSION_CREATE_CONN + ".pICR",
                                            null /*lock*/) {
                                @Override
                                public void loggedRun() {
                                    createConnection(
                                            connectionManagerPhoneAccount,
                                            id,
                                            request,
                                            isIncoming,
                                            isUnknown);
                                }
                            }.prepare());
                        } else {
                            createConnection(
                                    connectionManagerPhoneAccount,
                                    id,
                                    request,
                                    isIncoming,
                                    isUnknown);
                        }
                    } finally {
                        args.recycle();
                        Log.endSession();
                    }
                    break;
                }
            }
        }

}

步驟十二

通過遠程接口創建鏈接 ,利用遠程接口創建一個通話鏈接,等待12步驟完成,完成後會返回一連接對象connection對象傳入handlerCreateConnectionComplete()方法

 /**
     * This can be used by telecom to either create a new outgoing call or attach to an existing
     * incoming call. In either case, telecom will cycle through a set of services and call
     * createConnection util a connection service cancels the process or completes it successfully.
     */
    private void createConnection(
            final PhoneAccountHandle callManagerAccount,
            final String callId,
            final ConnectionRequest request,
            boolean isIncoming,
            boolean isUnknown) {
        boolean isLegacyHandover = request.getExtras() != null &&
                request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false);
        boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
                TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
        Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
                        "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b",
                callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover,
                isHandover);

        Connection connection = null;
        if (isHandover) {
            PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null
                    ? (PhoneAccountHandle) request.getExtras().getParcelable(
                    TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null;
            if (!isIncoming) {
                connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request);
            } else {
                connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request);
            }
        } else {
            connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
                    : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
                    : onCreateOutgoingConnection(callManagerAccount, request);
        }
        Log.d(this, "createConnection, connection: %s", connection);
        if (connection == null) {
            Log.i(this, "createConnection, implementation returned null connection.");
            connection = Connection.createFailedConnection(
                    new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
        }

        connection.setTelecomCallId(callId);
        if (connection.getState() != Connection.STATE_DISCONNECTED) {
            // 監聽 Connection 的狀態變化,通知 system 進程(CallsManager)更新
            // 通過 system 進程提供的 adapter 實現
            addConnection(request.getAccountHandle(), callId, connection);
        }

        Uri address = connection.getAddress();
        String number = address == null ? "null" : address.getSchemeSpecificPart();
        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
                Connection.toLogSafePhoneNumber(number),
                Connection.stateToString(connection.getState()),
                Connection.capabilitiesToString(connection.getConnectionCapabilities()),
                Connection.propertiesToString(connection.getConnectionProperties()));

        Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
        //mAdapter.handleCreateConnectionComplete 調用回到 ConnectionServiceWrapper 的 handleCreateConnectionComplete,
        // 然後回到 Call 的 handleCreateConnectionSuccess
        mAdapter.handleCreateConnectionComplete(// 通知 system 進程(即CallsManager) 電話鏈接已經創建完成
                callId,
                request,
                new ParcelableConnection(
                        request.getAccountHandle(),
                        connection.getState(),
                        connection.getConnectionCapabilities(),
                        connection.getConnectionProperties(),
                        connection.getSupportedAudioRoutes(),
                        connection.getAddress(),
                        connection.getAddressPresentation(),
                        connection.getCallerDisplayName(),
                        connection.getCallerDisplayNamePresentation(),
                        connection.getVideoProvider() == null ?
                                null : connection.getVideoProvider().getInterface(),
                        connection.getVideoState(),
                        connection.isRingbackRequested(),
                        connection.getAudioModeIsVoip(),
                        connection.getConnectTimeMillis(),
                        connection.getConnectElapsedTimeMillis(),
                        connection.getStatusHints(),
                        connection.getDisconnectCause(),
                        createIdList(connection.getConferenceables()),
                        connection.getExtras()));

        if (isIncoming && request.shouldShowIncomingCallUi() &&
                (connection.getConnectionProperties() & Connection.PROPERTY_SELF_MANAGED) ==
                        Connection.PROPERTY_SELF_MANAGED) {
            // Tell ConnectionService to show its incoming call UX.
            connection.onShowIncomingCallUi();
        }
        if (isUnknown) {
            triggerConferenceRecalculate();
        }
    }

 

通過Adapter.handleCreateConnectionComplete()回調進入ConnectionServiceWrapper.java裏面的Adapter內部類中

  @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); //framework/base/telecom回調後調用  更新UI

                    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();
            }
        }
   private void handleCreateConnectionComplete(
            String callId,
            ConnectionRequest request,
            ParcelableConnection connection) {
        // TODO: Note we are not using parameter "request", which is a side effect of our tacit
        // assumption that we have at most one outgoing connection attempt per ConnectionService.
        // This may not continue to be the case.   判斷是否連接成功連接只需要進行一次
        if (connection.getState() == Connection.STATE_DISCONNECTED) {
            // A connection that begins in the DISCONNECTED state is an indication of
            // failure to connect; we handle all failures uniformly
            removeCall(callId, connection.getDisconnectCause());
        } else {
            // Successful connection  連接成功
            if (mPendingResponses.containsKey(callId)) {
                mPendingResponses.remove(callId)
                        .handleCreateConnectionSuccess(mCallIdMapper, connection);
            }
        }

連接建立完畢  

 

結語:我語文沒及格過,湊活着看吧。代碼挺長,如果有錯誤請提出做 更正,如有內容在加,謝謝觀看 ,如果滿意點個贊吧 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章