关闭

Android 4.4 Kitkat Phone工作流程浅析(七)__来电(MT)响铃流程

535人阅读 评论(1) 收藏 举报
本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉。

前置文章:

Android 4.4 Kitkat Phone工作流程浅析(一)__概要和学习计划

Android 4.4 Kitkat Phone工作流程浅析(二)__UI结构分析

Android 4.4 Kitkat Phone工作流程浅析(三)__MO(去电)流程分析

Android 4.4 Kitkat Phone工作流程浅析(四)__RILJ工作流程简析

Android 4.4 Kitkat Phone工作流程浅析(五)__MT(来电)流程分析

Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程

概述

        本系列文章以MT/MO为主线流程,并对其中的细枝末节进行补充说明,比如来电响铃流程。在MT流程的分析中已经涵盖了流程的发起与终止,本文所描述的响铃流程始于MT流程的发起,如对MT流程不熟悉的童鞋请查看文章《Android 4.4 Kitkat Phone工作流程浅析(五)__MT(来电)流程分析》以及《Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程》。

        Android 4.4对于响铃流程有所改动,把响铃触发放到了TeleService中,这也符合4.4 Phone的设计风格即UI和Logic分离。当来电流程发起时,抓取radio_log进行分析后,可以看到整个来电过程状态改变如下:

  1. handleMessage (EVENT_VOICE_CALL_INCOMING_INDICATION) //设置voice call的标志  
  2. handleMessage (EVENT_NEW_RINGING_CONNECTION)         //新来电标志  
  3. handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)     //状态改变  
  4. handleMessage (EVENT_INCOMING_RING)                  //响铃  
  5. handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION) //收到AT指令CLIP后,更新通话信息(CLIP即来电显示作用)  
  6. handleMessage (EVENT_INCOMING_RING)  
  7. handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION)  
  8. handleMessage (EVENT_INCOMING_RING)  
  9. handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION)  
  10. handleMessage (EVENT_INCOMING_RING)  
  11. handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION)  
  12. handleMessage (EVENT_INCOMING_RING)  
  13. handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION)  
  14. handleMessage (EVENT_DISCONNECT)                     //断开连接  
  15. handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)     //状态改变  
  16. handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)  
这些log是CallManager中打印出来的,当然我们也可以在RIL.java中去查看对应的log。

当Modem侧收到来电消息后所做的操作如下

1. 根据来电类型设置INCOMING_INDICATION;

2. 发起NEW_RINGING_CONNECTION,新来电标志

3. 触发CALL_STATE_CHANGED状态,状态改变促使界面更新;

4. 发起响铃通知INCOMING_RING,响铃流程由此发起;

5. 根据CLIP返回信息触发CRSS_SUPP_SERVICE_NOTIFICATION,这是由MTK加入的,其作用是根据CLIP的返回更新Call的信息;

6. 循环上面4和5步,持续响铃;

7. 断开连接DISCONNECT,本次MT流程结束(未接听);

8. 触发CALL_STATE_CHANGED状态,状态改变促使界面更新;

Telephony Framework处理响铃事件

        当来电消息到来时,经过Modem以及RILD的处理后便传递到Telephony framework中,并由RILJ继续发起。流程图如下:

RILJ

        在MT流程发起后,会有相关的unsolicited信息反馈到RILJ中,这里主要关注响铃流程(ringing_flow),因此可以找到以下AT指令返回的log信息:

  1. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: +CRING: VOICE  
  2. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT:   
  3. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: +CLIP: "13800138000",0,"",0,"",0  
  4. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT:   
  5. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: +ECPI: 1,4,0,1,1,0,"13800138000",129,""  
  6. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: AT< +CRING: VOICE  
  7. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: RIL_URC_READER:+CRING: VOICE  
  8. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: RIL_URC_READER Enter processLine  
  9. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-RIL: Nw URC:+CRING: VOICE  
  10. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-RIL: receiving RING!!!!!!  
  11. 01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-RIL: receiving first RING!!!!!!  

整个radio_log中每隔3877ms便会打印一次+CRING: VOICE的log,通话挂断前一共有5次。根据AT返回信息可以知道,来电类型为Voice,并且是一次响铃事件。紧接着在RILJ中收到了与之对应的事件RIL_UNSOL_CALL_RING:

  1. 01-01 02:46:02.155 1443 1837 D RILJ : RIL(2) :[UNSL RIL]< UNSOL_CALL_RING [C@422899d0  
这里是UnSolicited事件,触发processUnsolicited方法,并执行到以下代码处:
  1. case RIL_UNSOL_CALL_RING:  
  2.     if (RILJ_LOGD) unsljLogRet(response, ret);  
  3.     if (mRingRegistrant != null) {  
  4.     //观察者模式  
  5.         mRingRegistrant.notifyRegistrant(  
  6.                 new AsyncResult (null, ret, null));  
  7.     }  
前面的文章中我们已经分析过Registrant这种触发模式,这里再次分析下notifyRegistrant()方法触发后的跳转地点。

首先查看到mRingRegistrant的定义在SourceCode/frameworks/opt/telephony/src/java/com/android/internal/telephony/BaseCommands.java中,且赋值在setOnCallRing方法中,如下:

  1. @Override  
  2. public void setOnCallRing(Handler h, int what, Object obj) {  
  3.     mRingRegistrant = new Registrant (h, what, obj);  
  4. }  
这里的setOnCallRing方法在SourceCode/frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneBase.java的构造方法中调用,如下:
  1. mCi.setOnCallRing(this, EVENT_CALL_RING, null);  
mCi是CommandsInterface的对象,而BaseCommands implements CommandsInterface,也就是说在PhoneBase的构造方法被调用时,就应经完成了对mRingRegistrant的赋值。
需要注意两点:

(1). PhoneBase extends Handler,因此setOnCallRing中的this即代表了PhoneBase本身。换句话说,当触发notifyRegistrant时,便会回调到PhoneBase的handleMessage中;

(2). setOnCallRing中的EVENT_CALL_RING表示当触发notifyRegistrant时,回调handleMessage中的相应case;

mRingRegistrant注册流程


        当Phone第一次启动时便会执行PhoneFactory中的makeDefaultPhone()方法,用于完成Phone的初始化,在初始化过程中即完成了mRingRegistrant的注册。

PhoneBase

        当执行mRingRegistrant.notifyRegistrant()方法后,跳转到PhoneBase的handleMessage方法中,代码如下:
  1. case EVENT_CALL_RING:  
  2.     ar = (AsyncResult)msg.obj;  
  3.     if (ar.exception == null) {  
  4.         PhoneConstants.State state = getState();  
  5.         if ((!mDoesRilSendMultipleCallRing)  
  6.                 && ((state == PhoneConstants.State.RINGING) ||  
  7.                         (state == PhoneConstants.State.IDLE))) {  
  8.             mCallRingContinueToken += 1;  
  9.             sendIncomingCallRingNotification(mCallRingContinueToken);  
  10.         } else {  
  11.             //执行这里  
  12.             notifyIncomingRing();  
  13.         }  
  14.     }  
  15.     break;  
这里继续查看notifyIncomingRing()方法:
  1. private void notifyIncomingRing() {  
  2.     if (!mIsVoiceCapable)  
  3.         return;  
  4.     AsyncResult ar = new AsyncResult(nullthisnull);  
  5.     mIncomingRingRegistrants.notifyRegistrants(ar);  
  6. }  
同样使用了观察者模式,查找相应的registerXXX方法,如下:
  1. @Override  
  2. public void registerForIncomingRing(  
  3.         Handler h, int what, Object obj) {  
  4.     checkCorrectThread(h);  
  5.     mIncomingRingRegistrants.addUnique(h, what, obj);  
  6. }  
可以找到在CallManager的registerForPhoneStates方法中,调用了IncomingRing的register方法:
  1. if (FeatureOption.MTK_GEMINI_SUPPORT == true && !(phone instanceof SipPhone)) {  
  2.     if(phone instanceof GeminiPhone) {  
  3.         int offset;        <span style="white-space:pre">   </span>  
  4.         int count = (MAXIMUM_SIM_COUNT < PhoneConstants.GEMINI_SIM_NUM) ? MAXIMUM_SIM_COUNT : PhoneConstants.GEMINI_SIM_NUM;  
  5.         Phone targetPhone;  
  6.         for (int i = 0; i < count; i++) {  
  7.             offset = i * NOTIFICATION_ID_OFFSET;  
  8.             targetPhone = ((GeminiPhone)phone).getPhonebyId(PhoneConstants.GEMINI_SIM_1 + i);      
  9.             //... ...省略  
  10.             targetPhone.registerForIncomingRing(mHandler, EVENT_INCOMING_RING + offset, null);  
这里涉及到MTK的双卡机制,同时可能大家会有疑惑如何断定是这里调用的呢?我们可以反向思考,当发现这里调用之后,我们反过来看是哪里调用了registerForPhoneStates,然后依次查看。最终我们可以看到这些都是在Phone初始化时候顺序调用的,我们只是反过来查找而已。

通过上面CallManager中的代码可以知道:

(1). mHandler在CallManager中定义,相关handleMessage即可找到;

(2). case对应事件为:EVENT_INCOMING_RING;

mIncomingRingRegistrants注册流程


        mIncomingRingRegistrants的注册流程始于Phone启动并初始化时,需要关注的一点是CallManager中的registerForPhoneStates()方法。为什么这里直接从CallManager跳转到PhoneBase呢?实际上targetPhone对象是通过PhoneProxy传递过来的,而PhoneProxy是GSMPhone和CDMAPhone的代理,GSMPhone和CDMAPhone都继承自PhoneBase,最终的实现也在PhoneBase中,这里省略了部分跳转,请读者知悉。

CallManager

        根据前面的分析,在PhoneBase的notifyIncomingRing()方法中会调用mIncomingRingRegistrants.notifyRegistrants()方法,可以找到在CallManager中对应的handleMessage方法以及对应的处理事件EVENT_INCOMING_RING:

  1. @Override  
  2. public void handleMessage(Message msg) {  
  3.     int index;  
  4.     switch (msg.what) {  
  5.         //... ...省略  
  6.         case EVENT_INCOMING_RING:  
  7.         case EVENT_INCOMING_RING + NOTIFICATION_ID_OFFSET:  
  8.         case EVENT_INCOMING_RING + (NOTIFICATION_ID_OFFSET * 2):  
  9.         case EVENT_INCOMING_RING + (NOTIFICATION_ID_OFFSET * 3):  
  10.             if (!hasActiveFgCall()) {  
  11.                index = (msg.what - EVENT_INCOMING_RING) / NOTIFICATION_ID_OFFSET;  
  12.                mIncomingRingRegistrantsGemini[index].notifyRegistrants((AsyncResult) msg.obj);  
  13.                mIncomingRingRegistrants.notifyRegistrants((AsyncResult) msg.obj);  
  14.             }  
  15.         break;  
看到这里继续查看mIncomingRingRegistrantsGemini和mIncomingRingRegistrants的registerXXX方法,代码如下:
  1. //mIncomingRingRegistrantsGemini的registe方法  
  2. public void registerForIncomingRingEx(Handler h, int what, Object obj, int simId){  
  3.       int index = getRegistrantsArrayIndex(simId);  
  4.       if (index != -1) {  
  5.         mIncomingRingRegistrantsGemini[index].addUnique(h, what, obj);  
  6.     }  
  7. }  
  8. //mIncomingRingRegistrants的registe方法  
  9. public void registerForIncomingRing(Handler h, int what, Object obj){  
  10.     mIncomingRingRegistrants.addUnique(h, what, obj);  
  11. }  
以上方法会根据手机制式来调用,如果是双卡则调用Gemini,在CallManagerWrapper中可以找到:
  1. public static void registerForIncomingRing(Handler handler, int what, Object obj) {  
  2.     if (GeminiUtils.isGeminiSupport()) {  
  3.         final int[] geminiSlots = GeminiUtils.getSlots();  
  4.         for (int geminiSlot : geminiSlots) {  
  5. <span style="white-space:pre">  </span>    //双卡  
  6.             CallManager.getInstance().registerForIncomingRingEx(handler, what, obj,  
  7.                     geminiSlot);  
  8.         }  
  9.     } else {  
  10. <span style="white-space:pre">  </span>//单卡  
  11.         CallManager.getInstance().registerForIncomingRing(handler, what, obj);  
  12.     }  
  13. }  
而以上方法在CallManagerWrapper中还经过了一层包装:
  1. public static void registerForIncomingRing(Handler handler, int what) {  
  2.     registerForIncomingRing(handler, what, null);  
  3. }  
那么后续调用是在哪里呢?我们可以在CallStateMonitor的registerForNotifications()方法中找到:
  1. CallManagerWrapper.registerForIncomingRing(this, PHONE_INCOMING_RING);  
而registerForNotifications()方法在CallStateMonitor初始化以及Radio状态改变的时候会调用。

通过以上分析我们可以知道:

(1). 在CallStateMonitor中注册了来电响铃回调,也就是这里的this。CallStateMonitor继承自Handler,那么CallManager中的notifyRegistrants()方法会跳转到CallStateMonitor中;

(2). 对应的case事件为:PHONE_INCOMING_RING;

mIncomingRingRegistrantsGemini注册流程

        整个注册流程是从TeleService启动时开始的,TeleService有监听BOOT_COMPLETE的广播,在随机启动之后便开始了整个注册流程。在第8步需要注意,双卡执行CallManager.getInstance().registerForIncomingRingEx(),单卡则执行CallManager.getInstance().registerForIncomingRing()。

TeleService处理响铃

        当Telephony Framework处理完响铃事件之后,会将响铃事件上报到CallStateMonitor中,并最终在TeleService中发起响铃操作,流程图如下:

CallStateMonitor

        经过framework的层层处理之后,响铃事件传递到了TeleService的CallStateMonitor中。通过前面的分析可以知道,在CallManager的handleMessage中,通过mIncomingRingRegistrantsGemini[index].notifyRegistrants()方法跳转到了CallStateMonitor的handleMessage中,如下:
  1. @Override  
  2. public void handleMessage(Message msg) {  
  3.     for (Handler handler : registeredHandlers) {  
  4.         handler.handleMessage(msg);  
  5.     }  
  6. }  
这里会根据registerHandler触发对应的回调,在前面来电(MT)流程的分析过程有看到,CallNotifier和CallModeler注册了CallStateMonitor的Handler回调,但通过进一步分析后发现,只有CallNotifier中才处理了PHONE_INCOMING_RING的事件,所以接着我们需要查看CallNotifier对响铃事件的处理。

CallNotifier

        在CallNotifier的handleMessage方法中找到PHONE_INCOING_RING的处理事件如下:
  1. @Override  
  2. public void handleMessage(Message msg) {  
  3.     //... ...省略  
  4.     case CallStateMonitor.PHONE_INCOMING_RING:  
  5.         log("PHONE_INCOMING_RING !");  
  6.         if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {  
  7.                 //... ...省略  
  8.                 if (provisioned && !isRespondViaSmsDialogShowing()) {  
  9.                     mRinger.ring();  
  10.                 }  
  11.             } else {  
  12.                 if (DBG) log("RING before NEW_RING, skipping");  
  13.             }  
  14.         }  
  15.     break;  
通过这里会调用Ringer的ring()方法,从而开始响铃。

Ringer

        到这里就应该准备播放铃声了,但ring()方法中还有一系列事情需要处理:
  1. void ring() {  
  2.     synchronized (this) {  
  3.         //... ...省略  
  4.         //创建Looper线程,用于播放/停止铃声,通过handleMessage接收播放/停止请求  
  5.         makeLooper();  
  6.         //如果是第一次播放则mFirstRingEventTime = -1  
  7.         if (mFirstRingEventTime < 0) {  
  8.         //这里获取系统开机后经过的时间,包括休眠  
  9.             mFirstRingEventTime = SystemClock.elapsedRealtime();  
  10.             if (mRingHandler != null) {  
  11.             //发起播放铃声的请求  
  12.                 mRingHandler.sendEmptyMessage(PLAY_RING_ONCE);  
  13.             }  
  14.         } else {  
  15.             //如果不是第一次播放  
  16.             if (mFirstRingStartTime > 0) {  
  17.                 if (mRingHandler != null) {  
  18.      //延迟发送播放请求,延迟时间为第一次启动播放时间减去第一次发送PLAY_RING_ONCE的时间(133ms)  
  19.                     mRingHandler.sendEmptyMessageDelayed(PLAY_RING_ONCE,  
  20.                         mFirstRingStartTime - mFirstRingEventTime);  
  21.                 }  
  22.             } else {  
  23.                 mFirstRingEventTime = SystemClock.elapsedRealtime();  
  24.             }  
  25.         }  
  26.     }  
  27. }  
在这里就要特别注意了,通过makeLooper()方法创建了一个Looper线程,用于播放/停止铃声,那这里为什么要用Looper呢?
后面通过sendEmptyMessage()和sendEmptyMessageDelayed()方法发起播放铃声的请求,接下来分析一下makeLooper()的构造以及使用Looper的缘由。

        makeLooper()方法主要创建一个ringer的线程,用于播放/停止铃声,代码如下:
  1. private void makeLooper() {  
  2.     //如果第一响铃mRingThread==null  
  3.     if (mRingThread == null) {  
  4.         //Worker实现了Runnable接口,在其构造方法Worker(String name)  
  5.         //中创建并启动了名为"ringer"的工作线程  
  6.         mRingThread = new Worker("ringer");  
  7.         //若还未获取到ringer线程的Looper对象则返回  
  8.         if (mRingThread.getLooper() == null) {  
  9.             return ;  
  10.         }  
  11.         //创建Handler并依附于ringer线程的Looper对象  
  12.         mRingHandler = new Handler(mRingThread.getLooper()) {  
  13.             @Override  
  14.             public void handleMessage(Message msg) {  
  15.                 Ringtone r = null;  
  16.                 switch (msg.what) {  
  17.                     case PLAY_RING_ONCE:  
  18.                         if (DBG) log("mRingHandler: PLAY_RING_ONCE...");  
  19.                         if (mRingtone == null && !hasMessages(STOP_RING)) {  
  20.                             // create the ringtone with the uri  
  21.                             if (DBG) log("creating ringtone: " + mCustomRingtoneUri);  
  22.                             r = RingtoneManager.getRingtone(mContext, mCustomRingtoneUri);  
  23.                             synchronized (Ringer.this) {  
  24.                                 if (!hasMessages(STOP_RING)) {  
  25.                                     mRingtone = r;  
  26.                                 }  
  27.                             }  
  28.                         }  
  29.                         r = mRingtone;  
  30.                         if (r != null && !hasMessages(STOP_RING) && !r.isPlaying()) {  
  31.                             PhoneLog.d(LOG_TAG, "play ringtone... ");  
  32.                             PhoneUtils.setAudioMode();  
  33.                             //播放铃声  
  34.                             r.play();  
  35.                             synchronized (Ringer.this) {  
  36.                //将第一次播放时间(开机后的时间包括休眠)赋值给mFirstRingStartTime  
  37.                                 if (mFirstRingStartTime < 0) {  
  38.                                     mFirstRingStartTime = SystemClock.elapsedRealtime();  
  39.                                 }  
  40.                             }  
  41.                         }  
  42.                         break;  
  43.                     case STOP_RING:  
  44.                         if (DBG) log("mRingHandler: STOP_RING...");  
  45.                         r = (Ringtone) msg.obj;  
  46.                         if (r != null) {  
  47.                             //停止播放铃声  
  48.                             r.stop();  
  49.                         } else {  
  50.                             if (DBG) log("- STOP_RING with null ringtone!  msg = " + msg);  
  51.                         }  
  52.                         //退出Looper循环  
  53.                         getLooper().quit();  
  54.                         break;  

makeLooper()主要做了三件事:
(1). 创建并启动名为ringer的Looper线程;
(2). 实例化mHandler并依附于ringer的Looper对象;
(3). 接收并处理播放/停止铃声的Message;
可以看到makeLooper做了相当重要的事情,特别是对于第三点,后续处理播/停止铃声便在handleMessage中。

接下来需要看看Looper,我们看到Worker类的实现,如下:
  1. private class Worker implements Runnable {  
  2.     //创建mLock锁  
  3.     private final Object mLock = new Object();  
  4.     private Looper mLooper;  
  5.   
  6.     Worker(String name) {  
  7.     //创建并启动名为"name"的线程  
  8.         Thread t = new Thread(nullthis, name);  
  9.         t.start();  
  10.         synchronized (mLock) {  
  11.             while (mLooper == null) {  
  12.                 try {  
  13.                 //阻塞直到前面的"name"线程已成功运行(执行了run方法),最大阻塞时间5s  
  14.                     mLock.wait(5000);  
  15.                 } catch (InterruptedException ex) {  
  16.                 }     
  17.             }     
  18.         }     
  19.     }  
  20.     public Looper getLooper() {  
  21.     //返回"name"线程的Looper对象  
  22.         return mLooper;  
  23.     }  
  24.     public void run() {  
  25.         synchronized (mLock) {  
  26.         //启用Looper  
  27.             Looper.prepare();  
  28.         //返回当前线程(也就是这里的子线程"name")的Looper对象  
  29.             mLooper = Looper.myLooper();  
  30.         //唤醒锁,不再阻塞  
  31.             mLock.notifyAll();  
  32.         }  
  33.     //开启Looper循环  
  34.         Looper.loop();  
  35.     }  
  36.     public void quit() {  
  37.     //退出Looper循环  
  38.         mLooper.quit();  
  39.     }     
  40. }  
Looper用于在线程中开启消息循环,普通线程默认是没有消息循环的,在线程中通过调用Looper.prepare()开启消息循环,通过Looper.loop()处理消息循环直到线程循环终止,我们可以调用Looper的quit()方法退出循环。
(关于Looper官方解释,StackOverFlow上的回答,或者查看CSDN网友分析)

        回到前面的问题,这里为什么要使用Looper呢?使用线程来播放铃声,同时又需要根据不同的状态,停止铃声的播放,也就是说当持续需要线程来做某件事情,同时又想控制该事情的启动与停止,这便可以使用Looper

        当第一次收到响铃请求时,开启线程启动Loop,并播放铃声。通过对Radio_log的分析,RILJ每隔3.877s收到一次响铃请求,此时上报并传到到TeleService中,如果此时铃声已经播放完毕,那么则继续执行ringtone.play()方法启动铃声的播放,如果之前的铃声并未播放完毕,则不会再次启动ringtone.play()方法。如果此时对方/己方挂断了来电,那么响铃需要立即终止,此时只需在子线程中处理STOP_RING方法即可。

总结

        整个响铃流程跟来电(MT)流程类似,但响铃并未涉及到界面的变换,在TeleService中调用到media中的ringtone.play()方法进行铃声的播放。特别需要注意的是整个过程中,大量的使用了观察者模式,最后使用Looper线程来做铃声播放/停止操作。

       文中涉及的时序图资源免积分下载,戳这里

整个流程图如下:
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:15328次
    • 积分:411
    • 等级:
    • 排名:千里之外
    • 原创:25篇
    • 转载:18篇
    • 译文:0篇
    • 评论:1条
    最新评论