Android电话拨打流程源码分析

来源:互联网 发布:一洋淘宝助手破解版 编辑:程序博客网 时间:2024/05/16 07:53

——转载至:《Android电话拨打流程源码分析》

前面分析了电话拨号界面及电话呼叫界面,由于Android的电话Phone设计的很复杂,因此先从UI层入手分析。想要了解Android的电话拨号UI,请查看Android电话拨号UI分析,电话拨号UI在Contacts包中。想要了解Android电话呼叫UI,请查看Android电话Phone UI分析,该UI在Phone包中,了解完电话想要UI后,还必须首先了解Android的Phone设计框架,Android电话Phone设计框架介绍介绍了Phone的框架设计及Phone进程的启动,本文以源码的形式介绍Android的电话拨打流程。点击Launcher上的拨号图标,首先进入电话拨号界面,前面已经分析了,该UI在Contacts包中,启动显示的是DialtactsActivity,关于DialtactsActivity的布局解析、UI布局在Android电话拨号UI分析中有详细的分析,这里不在重复介绍。我们从点击拨号按钮开始分析电话的拨号流程:

DialpadFragment.java

[html] view plaincopy
  1. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {  
  2.  ...  
  3. // Check whether we should show the onscreen "Dial" button.  
  4. mDialButton = mAdditionalButtonsRow.findViewById(R.id.dialButton);  
  5. if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {  
  6.     mDialButton.setOnClickListener(this);  
  7. } else {  
  8.     mDialButton.setVisibility(View.GONE); // It's VISIBLE by default  
  9.     mDialButton = null;  
  10. }  
  11. ...  

拨号按钮的单击事件响应:

[java] view plaincopy
  1. public void onClick(View view) {  
  2.     switch (view.getId()) {  
  3.         case R.id.dialButton: {  
  4.             mHaptic.vibrate();   
  5.             dialButtonPressed();  
  6.             return;  
  7.         }  
  8.         ...  
  9.     }  
  10. }  

调用dialButtonPressed()函数发起电话呼叫

[java] view plaincopy
  1. public void dialButtonPressed() {  
  2.     if(mDigits == null){  
  3.         Log.e(TAG,"dialButtonPressed,mDigits == null");  
  4.         return;  
  5.     }  
  6.     //未输入号码处理  
  7.     if (isDigitsEmpty()) {  
  8.         handleDialButtonClickWithEmptyDigits();  
  9.     } else {  
  10.         final String number = mDigits.getText().toString();  
  11.         // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated  
  12.         // test equipment.  
  13.         if (number != null&& !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)  
  14.                 && number.matches(mProhibitedPhoneNumberRegexp)  
  15.                 && (SystemProperties.getInt("persist.radio.otaspdial"0) != 1)) {  
  16.             Log.i(TAG, "The phone number is prohibited explicitly by a rule.");  
  17.             if (getActivity() != null) {  
  18.                 DialogFragment dialogFragment = ErrorDialogFragment.newInstance(  
  19.                                 R.string.dialog_phone_call_prohibited_title);  
  20.                 dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");  
  21.             }  
  22.             //号码输入不正确.  
  23.             mDigits.getText().clear();  
  24.         } else if(number != null && (number.startsWith(",") || number.startsWith(";"))){  
  25.             mDigits.getText().clear();  
  26.             if (getActivity() != null) {  
  27.                 Toast.makeText(getActivity(), getText(R.string.invalid_number),  
  28.                         Toast.LENGTH_SHORT).show();  
  29.             }  
  30.         } else {  
  31.             //启动电话呼叫界面  
  32.             final Intent intent = ContactsUtils.getCallIntent(number,(getActivity() instanceof DialtactsActivity ?  
  33.                             ((DialtactsActivity)getActivity()).getCallOrigin() : null));  
  34.             startActivity(intent);  
  35.             mClearDigitsOnStop = true;  
  36.             mDigits.getText().clear();  
  37.             if(mFlagIntentNumber){  
  38.                 getActivity().finish();  
  39.             }  
  40.         }  
  41.     }  
  42. }  

函数首先对输入的号码进行检查,如果没有输入号码,直接按下拨号按钮,则调用handleDialButtonClickWithEmptyDigits函数来处理

[java] view plaincopy
  1. private void handleDialButtonClickWithEmptyDigits() {  
  2.     if (phoneIsCdma() && phoneIsOffhook()) {  
  3.         // This is really CDMA specific. On GSM is it possible  
  4.         // to be off hook and wanted to add a 3rd party using  
  5.         // the redial feature.  
  6.         startActivity(newFlashIntent());  
  7.     } else {  
  8.         if (mDigits != null && !TextUtils.isEmpty(mLastNumberDialed)) {  
  9.             // Recall the last number dialed.  
  10.             mDigits.setText(mLastNumberDialed);  
  11.   
  12.             // ...and move the cursor to the end of the digits string,  
  13.             // so you'll be able to delete digits using the Delete  
  14.             // button (just as if you had typed the number manually.)  
  15.             //  
  16.             // Note we use mDigits.getText().length() here, not  
  17.             // mLastNumberDialed.length(), since the EditText widget now  
  18.             // contains a *formatted* version of mLastNumberDialed (due to  
  19.             // mTextWatcher) and its length may have changed.  
  20.             mDigits.setSelection(mDigits.getText().length());  
  21.         } else {  
  22.             // There's no "last number dialed" or the  
  23.             // background query is still running. There's  
  24.             // nothing useful for the Dial button to do in  
  25.             // this case.  Note: with a soft dial button, this  
  26.             // can never happens since the dial button is  
  27.             // disabled under these conditons.  
  28.             playTone(ToneGenerator.TONE_PROP_NACK);  
  29.         }  
  30.     }  
  31. }  

如果号码输入正确合法,则使用ContactsUtils工具类来创建一个Intent。

DialtactsActivity.java

[java] view plaincopy
  1. public String getCallOrigin() {  
  2.     return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null;  
  3. }  
  4.   
  5. /** Returns true if the given intent contains a phone number to populate the dialer with */  
  6. private boolean isDialIntent(Intent intent) {  
  7.     final String action = intent.getAction();  
  8.     if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {  
  9.         return true;  
  10.     }  
  11.     if (Intent.ACTION_VIEW.equals(action)) {  
  12.         final Uri data = intent.getData();  
  13.         if (data != null && Constants.SCHEME_TEL.equals(data.getScheme())) {  
  14.             return true;  
  15.         }  
  16.     }  
  17.     return false;  
  18. }  

从Launcher点击拨号图标进入的,因此isDialIntent返回true,getCallOrigin返回null

ContactsUtils.java

[java] view plaincopy
  1. public static Intent getCallIntent(String number, String callOrigin) {  
  2.     return getCallIntent(getCallUri(number), callOrigin);  
  3. }  
  4.   
  5. public static Intent getCallIntent(Uri uri, String callOrigin) {  
  6.     final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri);  
  7.     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  8.     if (callOrigin != null) {  
  9.         intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN, callOrigin);  
  10.     }  
  11.     return intent;  
  12. }  

action为Intent.ACTION_CALL_PRIVILEGED,因此使用隐式启动OutgoingCallBroadcaster

因此Phone进程中的OutgoingCallBroadcaster将被启动。google对电话拨号步骤有详细的说明:

/*
 * Here's the most typical outgoing call sequence:
 *
 *  (1) OutgoingCallBroadcaster receives a CALL intent and sends the
 *      NEW_OUTGOING_CALL broadcast
 *
 *  (2) The broadcast finally reaches OutgoingCallReceiver, which stashes
 *      away a copy of the original CALL intent and launches
 *      SipCallOptionHandler
 *
 *  (3) SipCallOptionHandler decides whether this is a PSTN or SIP call (and
 *      in some cases brings up a dialog to let the user choose), and
 *      ultimately calls CallController.placeCall() (from the
 *      setResultAndFinish() method) with the stashed-away intent from step
 *      (2) as the "intent" parameter.
 *
 *  (4) Here in CallController.placeCall() we read the phone number or SIP
 *      address out of the intent and actually initiate the call, and
 *      simultaneously launch the InCallScreen to display the in-call UI.
 *
 *  (5) We handle various errors by directing the InCallScreen to
 *      display error messages or dialogs (via the InCallUiState
 *      "pending call status code" flag), and in some cases we also
 *      sometimes continue working in the background to resolve the
 *      problem (like in the case of an emergency call while in
 *      airplane mode).  Any time that some onscreen indication to the
 *      user needs to change, we update the "status dialog" info in
 *      the inCallUiState and (re)launch the InCallScreen to make sure
 *      it's visible.
 */


如OutgoingCallBroadcaster接收 CALL 和CALL_PRIVILEGED 两种Intents,然后广播出ACTION_NEW_OUTGOING_CALL intent,让别的应用程序有机会去监视这些intent,最后这些呼叫intent又被自己收到转换,启动InCallScreen.

src\com\android\phone\OutgoingCallBroadcaster.java

[java] view plaincopy
  1. protected void onCreate(Bundle icicle) {  
  2.     super.onCreate(icicle);  
  3.     setContentView(R.layout.outgoing_call_broadcaster);  
  4.     mWaitingSpinner = (ProgressBar) findViewById(R.id.spinner);  
  5.   
  6.     Intent intent = getIntent();  
  7.     if (DBG) {  
  8.         final Configuration configuration = getResources().getConfiguration();  
  9.         Log.v(TAG, "onCreate: this = " + this + ", icicle = " + icicle);  
  10.         Log.v(TAG, " - getIntent() = " + intent);  
  11.         Log.v(TAG, " - configuration = " + configuration);  
  12.     }  
  13.   
  14.     if (icicle != null) {  
  15.         //icicle不为空,表示重新初始化先前关闭的OutgoingCallBroadcaster,  
  16.         // In practice this happens very rarely (because the lifetime  
  17.         // of this activity is so short!), but it *can* happen if the  
  18.         // framework detects a configuration change at exactly the  
  19.         // right moment;   
  20.         // In this case, do nothing.  Our onCreate() method has already  
  21.         // run once (with icicle==null the first time), which means  
  22.         // that the NEW_OUTGOING_CALL broadcast for this new call has  
  23.         // already been sent.  
  24.         Log.i(TAG, "onCreate: non-null icicle!  "  
  25.               + "Bailing out, not sending NEW_OUTGOING_CALL broadcast...");  
  26.         return;  
  27.     }  
  28.     //处理得到的intent  
  29.     processIntent(intent);  
  30.     if (DBG) Log.v(TAG, "At the end of onCreate(). isFinishing(): " + isFinishing());  
  31. }  

函数直接调用processIntent函数处理前面发送过来的intent,该方法可以处理以下三种actions,

CALL (action for usual outgoing voicecalls)

CALL_PRIVILEGED (can come from built-inapps like contacts / voice dialer / bluetooth)

CALL_EMERGENCY (from the EmergencyDialerthat's reachable from the lockscreen.)

对于数据为tel: URI的电话处理流程为:OutgoingCallReceiver -> SipCallOptionHandler ->InCallScreen.

对于数据为sip: URI的网络电话,则跳过NEW_OUTGOING_CALL广播,直接调用SipCallOptionHandler处理

对于数据为voicemail: URIs的语音信箱处理同电话处理流程类似

[java] view plaincopy
  1. private void processIntent(Intent intent) {  
  2.     if (DBG) {  
  3.         Log.v(TAG, "processIntent() = " + intent + ", thread: " + Thread.currentThread());  
  4.     }  
  5.     final Configuration configuration = getResources().getConfiguration();  
  6.   
  7.     // 电话拨号只对具有语音通信能力的设备而言  
  8.     if (!PhoneGlobals.sVoiceCapable) {  
  9.         Log.i(TAG, "This device is detected as non-voice-capable device.");  
  10.         handleNonVoiceCapable(intent);  
  11.         return;  
  12.     }  
  13.     //得到相应的Action  
  14.     String action = intent.getAction();  
  15.     //从Intent中取出电话号码  
  16.     String number = PhoneNumberUtils.getNumberFromIntent(intent, this);  
  17.     //电话号码检查  
  18.     if (number != null) {  
  19.         if (!PhoneNumberUtils.isUriNumber(number)) {  
  20.             //根据键盘map将字符转换为相应的数字  
  21.             number = PhoneNumberUtils.convertKeypadLettersToDigits(number);  
  22.             number = PhoneNumberUtils.stripSeparators(number);  
  23.         }  
  24.     } else {  
  25.         Log.w(TAG, "The number obtained from Intent is null.");  
  26.     }  
  27.     // 如果callNow为true,表示当前为不允许拦截的如紧急拨号,这种情形下就无需这NEW_OUTGOING_CALL流程  
  28.     boolean callNow;  
  29.       
  30.     if (getClass().getName().equals(intent.getComponent().getClassName())) {  
  31.         // If we were launched directly from the OutgoingCallBroadcaster,  
  32.         // not one of its more privileged aliases, then make sure that  
  33.         // only the non-privileged actions are allowed.  
  34.         if (!Intent.ACTION_CALL.equals(intent.getAction())) {  
  35.             Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL");  
  36.             intent.setAction(Intent.ACTION_CALL);  
  37.         }  
  38.     }  
  39.   
  40.     // 检查当前号码是否为紧急号码,只有CALL_PRIVILEGED和CALL_EMERGENCY类型的intent才允许拨打紧急号码  
  41.     // (Note that the ACTION_CALL check below depends on the result of  
  42.     // isPotentialLocalEmergencyNumber() rather than just plain  
  43.     // isLocalEmergencyNumber()  
  44.     // 100%确保第三方应用不允许通过传递如"9111234" 这种无效号码来拨打紧急号码   
  45.     final boolean isExactEmergencyNumber =(number != null) && PhoneNumberUtils.isLocalEmergencyNumber(number, this);  
  46.     final boolean isPotentialEmergencyNumber = (number != null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, this);  
  47.     if (VDBG) {  
  48.         Log.v(TAG, " - Checking restrictions for number '" + number + "':");  
  49.         Log.v(TAG, "     isExactEmergencyNumber     = " + isExactEmergencyNumber);  
  50.         Log.v(TAG, "     isPotentialEmergencyNumber = " + isPotentialEmergencyNumber);  
  51.     }  
  52.   
  53.     if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {  
  54.         if (isPotentialEmergencyNumber) {  
  55.             Log.i(TAG, "ACTION_CALL_PRIVILEGED is used while the number is a potential"  
  56.                     + " emergency number. Use ACTION_CALL_EMERGENCY as an action instead.");  
  57.             action = Intent.ACTION_CALL_EMERGENCY;  
  58.         } else {  
  59.             action = Intent.ACTION_CALL;  
  60.         }  
  61.         if (DBG) Log.v(TAG, " - updating action from CALL_PRIVILEGED to " + action);  
  62.         intent.setAction(action);  
  63.     }  
  64.     //如果普通拨打的号码为紧急号码,则启动电话拨号器  
  65.     if (Intent.ACTION_CALL.equals(action)) {  
  66.         if (isPotentialEmergencyNumber) {  
  67.             Log.w(TAG, "Cannot call potential emergency number '" + number  
  68.                     + "' with CALL Intent " + intent + ".");  
  69.             Log.i(TAG, "Launching default dialer instead...");  
  70.             //启动默认的电话拨号器DialtactsActivity  
  71.             Intent invokeFrameworkDialer = new Intent();  
  72.             invokeFrameworkDialer.setClassName("com.android.contacts","com.android.contacts.DialtactsActivity");  
  73.             invokeFrameworkDialer.setAction(Intent.ACTION_DIAL);  
  74.             invokeFrameworkDialer.setData(intent.getData());  
  75.             if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: " + invokeFrameworkDialer);  
  76.             startActivity(invokeFrameworkDialer);  
  77.             finish();  
  78.             return;  
  79.         }  
  80.         callNow = false;  
  81.     //如果是紧急拨号,1.通过紧急拨号器拨号;2.ACTION_CALL_PRIVILEGED拨打紧急号码;将callNow设置为true  
  82.     } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {  
  83.         if (!isPotentialEmergencyNumber) {  
  84.             Log.w(TAG, "Cannot call non-potential-emergency number " + number  
  85.                     + " with EMERGENCY_CALL Intent " + intent + "."  
  86.                     + " Finish the Activity immediately.");  
  87.             finish();  
  88.             return;  
  89.         }  
  90.         callNow = true;  
  91.     } else {  
  92.         Log.e(TAG, "Unhandled Intent " + intent + ". Finish the Activity immediately.");  
  93.         finish();  
  94.         return;  
  95.     }  
  96.     //唤醒屏幕  
  97.     PhoneGlobals.getInstance().wakeUpScreen();  
  98.     // If number is null, we're probably trying to call a non-existent voicemail number,  
  99.     // send an empty flash or something else is fishy.  Whatever the problem, there's no  
  100.     // number, so there's no point in allowing apps to modify the number.  
  101.     if (TextUtils.isEmpty(number)) {  
  102.         if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) {  
  103.             Log.i(TAG, "onCreate: SEND_EMPTY_FLASH...");  
  104.             PhoneUtils.sendEmptyFlash(PhoneGlobals.getPhone());  
  105.             finish();  
  106.             return;  
  107.         } else {  
  108.             Log.i(TAG, "onCreate: null or empty number, setting callNow=true...");  
  109.             callNow = true;  
  110.         }  
  111.     }  
  112.     //如果是紧急拨号,直接启动拨号界面  
  113.     if (callNow) {  
  114.         Log.i(TAG, "onCreate(): callNow case! Calling placeCall(): " + intent);  
  115.         // Initiate the outgoing call, and simultaneously launch the  
  116.         // InCallScreen to display the in-call UI:  
  117.         PhoneGlobals.getInstance().callController.placeCall(intent);  
  118.     }  
  119.     // Remember the call origin so that users will be able to see an appropriate screen  
  120.     // after the phone call. This should affect both phone calls and SIP calls.  
  121.     final String callOrigin = intent.getStringExtra(PhoneGlobals.EXTRA_CALL_ORIGIN);  
  122.     if (callOrigin != null) {  
  123.         if (DBG) Log.v(TAG, " - Call origin is passed (" + callOrigin + ")");  
  124.         PhoneGlobals.getInstance().setLatestActiveCallOrigin(callOrigin);  
  125.     } else {  
  126.         if (DBG) Log.v(TAG, " - Call origin is not passed. Reset current one.");  
  127.         PhoneGlobals.getInstance().resetLatestActiveCallOrigin();  
  128.     }  
  129.     // For now, SIP calls will be processed directly without a  
  130.     // NEW_OUTGOING_CALL broadcast.  
  131.     //  
  132.     // TODO: In the future, though, 3rd party apps *should* be allowed to  
  133.     // intercept outgoing calls to SIP addresses as well.  To do this, we should  
  134.     // (1) update the NEW_OUTGOING_CALL intent documentation to explain this  
  135.     // case, and (2) pass the outgoing SIP address by *not* overloading the  
  136.     // EXTRA_PHONE_NUMBER extra, but instead using a new separate extra to hold  
  137.     // the outgoing SIP address.  (Be sure to document whether it's a URI or just  
  138.     // a plain address, whether it could be a tel: URI, etc.)  
  139.     Uri uri = intent.getData();  
  140.     String scheme = uri.getScheme();  
  141.     if (Constants.SCHEME_SIP.equals(scheme) || PhoneNumberUtils.isUriNumber(number)) {  
  142.         Log.i(TAG, "The requested number was detected as SIP call.");  
  143.         startSipCallOptionHandler(this, intent, uri, number);  
  144.         finish();  
  145.         return;  
  146.     }  
  147.       
  148.     Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);  
  149.     if (number != null) {  
  150.         broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);  
  151.     }  
  152.     PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent);  
  153.     broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow);  
  154.     broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString());  
  155.     // Need to raise foreground in-call UI as soon as possible while allowing 3rd party app  
  156.     // to intercept the outgoing call.  
  157.     broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);  
  158.     if (DBG) Log.v(TAG, " - Broadcasting intent: " + broadcastIntent + ".");  
  159.     //发送超时消息,当OutgoingCallReceiver在指定的时间内还未接受到广播时,显示超时  
  160.     mHandler.sendEmptyMessageDelayed(EVENT_OUTGOING_CALL_TIMEOUT,  
  161.             OUTGOING_CALL_TIMEOUT_THRESHOLD);  
  162.     //发送ACTION_NEW_OUTGOING_CALL广播  
  163.     sendOrderedBroadcastAsUser(broadcastIntent, UserHandle.OWNER,  
  164.             PERMISSION, new OutgoingCallReceiver(),  
  165.             null,  // scheduler  
  166.             Activity.RESULT_OK,  // initialCode  
  167.             number,  // initialData: initial value for the result data  
  168.             null);  // initialExtras  
  169. }  
首先获取Intent对象,获取拨出的号码。接着判断号码是否为紧急号码,如果是紧急号码,将callNow变量赋值为true,启动InCallScreen,并发送Intent.ACTION_NEW_OUTGOING_CALL广播。
[java] view plaincopy
  1. public void onReceive(Context context, Intent intent) {  
  2.     mHandler.removeMessages(EVENT_OUTGOING_CALL_TIMEOUT);  
  3.     doReceive(context, intent);  
  4.     if (DBG) Log.v(TAG, "OutgoingCallReceiver is going to finish the Activity itself.");  
  5.     finish();  
  6. }  
直接调用函数doReceive函数来处理ntent.ACTION_NEW_OUTGOING_CALL广播
[java] view plaincopy
  1. public void doReceive(Context context, Intent intent) {  
  2.     if (DBG) Log.v(TAG, "doReceive: " + intent);  
  3.     boolean alreadyCalled;  
  4.     String number;  
  5.     String originalUri;  
  6.     alreadyCalled = intent.getBooleanExtra(OutgoingCallBroadcaster.EXTRA_ALREADY_CALLED, false);  
  7.     if (alreadyCalled) {  
  8.         if (DBG) Log.v(TAG, "CALL already placed -- returning.");  
  9.         return;  
  10.     }  
  11.     number = getResultData();  
  12.     if (VDBG) Log.v(TAG, "- got number from resultData: '" + number + "'");  
  13.       
  14.     final PhoneGlobals app = PhoneGlobals.getInstance();  
  15.     //如果电话支持Otasp  
  16.     if (TelephonyCapabilities.supportsOtasp(app.phone)) {  
  17.         boolean activateState = (app.cdmaOtaScreenState.otaScreenState  
  18.                 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);  
  19.         boolean dialogState = (app.cdmaOtaScreenState.otaScreenState  
  20.                 == OtaUtils.CdmaOtaScreenState.OtaScreenState  
  21.                 .OTA_STATUS_SUCCESS_FAILURE_DLG);  
  22.         boolean isOtaCallActive = false;  
  23.   
  24.         if ((app.cdmaOtaScreenState.otaScreenState  
  25.                 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_PROGRESS)  
  26.                 || (app.cdmaOtaScreenState.otaScreenState  
  27.                 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING)) {  
  28.             isOtaCallActive = true;  
  29.         }  
  30.   
  31.         if (activateState || dialogState) {  
  32.             // The OTASP sequence is active, but either (1) the call  
  33.             // hasn't started yet, or (2) the call has ended and we're  
  34.             // showing the success/failure screen.  In either of these  
  35.             // cases it's OK to make a new outgoing call, but we need  
  36.             // to take down any OTASP-related UI first.  
  37.             if (dialogState) app.dismissOtaDialogs();  
  38.             app.clearOtaState();  
  39.             app.clearInCallScreenMode();  
  40.         } else if (isOtaCallActive) {  
  41.             // The actual OTASP call is active.  Don't allow new  
  42.             // outgoing calls at all from this state.  
  43.             Log.w(TAG, "OTASP call is active: disallowing a new outgoing call.");  
  44.             return;  
  45.         }  
  46.     }  
  47.     if (number == null) {  
  48.         if (DBG) Log.v(TAG, "CALL cancelled (null number), returning...");  
  49.         return;  
  50.     } else if (TelephonyCapabilities.supportsOtasp(app.phone)  
  51.             && (app.phone.getState() != PhoneConstants.State.IDLE)  
  52.             && (app.phone.isOtaSpNumber(number))) {  
  53.         if (DBG) Log.v(TAG, "Call is active, a 2nd OTA call cancelled -- returning.");  
  54.         return;  
  55.     } else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, context)) {  
  56.         Log.w(TAG, "Cannot modify outgoing call to emergency number " + number + ".");  
  57.         return;  
  58.     }  
  59.     originalUri = intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_ORIGINAL_URI);  
  60.     if (originalUri == null) {  
  61.         Log.e(TAG, "Intent is missing EXTRA_ORIGINAL_URI -- returning.");  
  62.         return;  
  63.     }  
  64.     Uri uri = Uri.parse(originalUri);  
  65.     number = PhoneNumberUtils.convertKeypadLettersToDigits(number);  
  66.     number = PhoneNumberUtils.stripSeparators(number);  
  67.   
  68.     if (DBG) Log.v(TAG, "doReceive: proceeding with call...");  
  69.     if (VDBG) Log.v(TAG, "- uri: " + uri);  
  70.     if (VDBG) Log.v(TAG, "- actual number to dial: '" + number + "'");  
  71.     startSipCallOptionHandler(context, intent, uri, number);  
  72. }  

OutgoingCallReceiver是OutgoingCallBroadcaster的一个内部类,作用是接收OutgoingCallBroadcaster发送的广播,判断是否已经启动InCallScreen。没有启动的话就进行一些初始化,如:对OTA进行初始化。接收到广播之后,从Intent里面取出电话号码及其URi。然后设置Intent为ACTION_CALL,并带上号码和uri。启动InCallScreen。关闭OutgoingCallReceiver。

OTA:Over-the-Air Technology 空中下载技术,是通过移动通信(GSM或CDMA)的空中接口对SIM卡数据及应用进行远程管理的技术。空中接口可以采用WAP、GPRS、CDMA1X及短消息技术。OTA技术的应用,使得移动通信不仅可以提供语音和数据服务,而且还能提供新业务下载。

[java] view plaincopy
  1. private void startSipCallOptionHandler(Context context, Intent intent,  
  2.         Uri uri, String number) {  
  3.     if (VDBG) {  
  4.         Log.i(TAG, "startSipCallOptionHandler...");  
  5.         Log.i(TAG, "- intent: " + intent);  
  6.         Log.i(TAG, "- uri: " + uri);  
  7.         Log.i(TAG, "- number: " + number);  
  8.     }  
  9.     //创建原始电话拨号intent的副本  
  10.     Intent newIntent = new Intent(Intent.ACTION_CALL, uri);  
  11.     newIntent.putExtra(EXTRA_ACTUAL_NUMBER_TO_DIAL, number);  
  12.     PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent);  
  13.   
  14.     Intent selectPhoneIntent = new Intent(ACTION_SIP_SELECT_PHONE, uri);  
  15.     selectPhoneIntent.setClass(context, SipCallOptionHandler.class);  
  16.     selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent);  
  17.     selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  18.     if (DBG) {  
  19.         Log.v(TAG, "startSipCallOptionHandler(): " +  
  20.                 "calling startActivity: " + selectPhoneIntent);  
  21.     }  
  22.     //启动电话类型选择界面  
  23.     context.startActivity(selectPhoneIntent);  
  24. }  
电话类型选择处理:1.读取用户设置;2.弹出对话框让用户选择
src\com\android\phone\SipCallOptionHandler.java
[java] view plaincopy
  1. public void onCreate(Bundle savedInstanceState) {  
  2. super.onCreate(savedInstanceState);  
  3. Intent intent = getIntent();  
  4. String action = intent.getAction();  
  5. if (!OutgoingCallBroadcaster.ACTION_SIP_SELECT_PHONE.equals(action)) {  
  6.     Log.wtf(TAG, "onCreate: got intent action '" + action + "', expected "  
  7.             + OutgoingCallBroadcaster.ACTION_SIP_SELECT_PHONE);  
  8.     finish();  
  9.     return;  
  10. }  
  11. //取出原始电话拨号intent的副本  
  12. mIntent = (Intent) intent.getParcelableExtra(OutgoingCallBroadcaster.EXTRA_NEW_CALL_INTENT);  
  13. if (mIntent == null) {  
  14.     finish();  
  15.     return;  
  16. }  
  17. // Allow this activity to be visible in front of the keyguard.  
  18. getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);  
  19. // - If it's a sip: URI, this is definitely a SIP call, regardless  
  20. //   of whether the data is a SIP address or a regular phone  
  21. //   number.  
  22. // - If this is a tel: URI but the data contains an "@" character  
  23. //   (see PhoneNumberUtils.isUriNumber()) we consider that to be a  
  24. //   SIP number too.  
  25. boolean voipSupported = PhoneUtils.isVoipSupported();  
  26. if (DBG) Log.v(TAG, "voipSupported: " + voipSupported);  
  27. mSipProfileDb = new SipProfileDb(this);  
  28. mSipSharedPreferences = new SipSharedPreferences(this);  
  29. mCallOption = mSipSharedPreferences.getSipCallOption();  
  30. if (DBG) Log.v(TAG, "Call option: " + mCallOption);  
  31. Uri uri = mIntent.getData();  
  32. String scheme = uri.getScheme();  
  33. mNumber = PhoneNumberUtils.getNumberFromIntent(mIntent, this);  
  34. boolean isInCellNetwork = PhoneGlobals.getInstance().phoneMgr.isRadioOn();  
  35. boolean isKnownCallScheme = Constants.SCHEME_TEL.equals(scheme)  
  36.         || Constants.SCHEME_SIP.equals(scheme);  
  37. boolean isRegularCall = Constants.SCHEME_TEL.equals(scheme)  
  38.         && !PhoneNumberUtils.isUriNumber(mNumber);  
  39. // Bypass the handler if the call scheme is not sip or tel.  
  40. if (!isKnownCallScheme) {  
  41.     setResultAndFinish();  
  42.     return;  
  43. }  
  44. // Check if VoIP feature is supported.  
  45. if (!voipSupported) {  
  46.     if (!isRegularCall) {  
  47.         showDialog(DIALOG_NO_VOIP);  
  48.     } else {  
  49.         setResultAndFinish();  
  50.     }  
  51.     return;  
  52. }  
  53. if (!PhoneUtils.hasPhoneProviderExtras(mIntent)) {  
  54.     if (!isNetworkConnected()) {  
  55.         if (!isRegularCall) {  
  56.             //显示无网络错误提示对话框  
  57.             showDialog(DIALOG_NO_INTERNET_ERROR);  
  58.             return;  
  59.         }  
  60.     } else {  
  61.         if (mCallOption.equals(Settings.System.SIP_ASK_ME_EACH_TIME)  
  62.                 && isRegularCall && isInCellNetwork) {  
  63.             //显示电话类型选择对话框  
  64.             showDialog(DIALOG_SELECT_PHONE_TYPE);  
  65.             return;  
  66.         }  
  67.         if (!mCallOption.equals(Settings.System.SIP_ADDRESS_ONLY)  
  68.                 || !isRegularCall) {  
  69.             mUseSipPhone = true;  
  70.         }  
  71.     }  
  72. }  
  73. if (mUseSipPhone) {  
  74.     // If there is no sip profile and it is a regular call, then we  
  75.     // should use pstn network instead.  
  76.     if ((mSipProfileDb.getProfilesCount() > 0) || !isRegularCall) {  
  77.         startGetPrimarySipPhoneThread();  
  78.         return;  
  79.     } else {  
  80.         mUseSipPhone = false;  
  81.     }  
  82. }  
  83. setResultAndFinish();  
选择SIP拨号还是PSTN拨号
[java] view plaincopy
  1. private void setResultAndFinish() {  
  2.     runOnUiThread(new Runnable() {  
  3.         public void run() {  
  4.             if (mOutgoingSipProfile != null) {  
  5.                 if (!isNetworkConnected()) {  
  6.                     showDialog(DIALOG_NO_INTERNET_ERROR);  
  7.                     return;  
  8.                 }  
  9.                 if (DBG) Log.v(TAG, "primary SIP URI is " +  
  10.                         mOutgoingSipProfile.getUriString());  
  11.                 createSipPhoneIfNeeded(mOutgoingSipProfile);  
  12.                 mIntent.putExtra(OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI,  
  13.                         mOutgoingSipProfile.getUriString());  
  14.                 if (mMakePrimary) {  
  15.                     mSipSharedPreferences.setPrimaryAccount(  
  16.                             mOutgoingSipProfile.getUriString());  
  17.                 }  
  18.             }  
  19.             //mUseSipPhone在SipCallOptionHandler的onCreate函数中被设置为false  
  20.             if (mUseSipPhone && mOutgoingSipProfile == null) {  
  21.                 showDialog(DIALOG_START_SIP_SETTINGS);  
  22.                 return;  
  23.             } else {  
  24.                 // Woo hoo -- it's finally OK to initiate the outgoing call!  
  25.                 PhoneGlobals.getInstance().callController.placeCall(mIntent);  
  26.             }  
  27.             finish();  
  28.         }  
  29.     });  
  30. }  
src\com\android\phone\CallController.java
[java] view plaincopy
  1. public void placeCall(Intent intent) {  
  2.     log("placeCall()...  intent = " + intent);  
  3.     if (VDBG) log("extras = " + intent.getExtras());  
  4.     final InCallUiState inCallUiState = mApp.inCallUiState;  
  5.   
  6.     if (intent == null) {  
  7.         Log.wtf(TAG, "placeCall: called with null intent");  
  8.         throw new IllegalArgumentException("placeCall: called with null intent");  
  9.     }  
  10.   
  11.     String action = intent.getAction();  
  12.     Uri uri = intent.getData();  
  13.     if (uri == null) {  
  14.         Log.wtf(TAG, "placeCall: intent had no data");  
  15.         throw new IllegalArgumentException("placeCall: intent had no data");  
  16.     }  
  17.   
  18.     String scheme = uri.getScheme();  
  19.     String number = PhoneNumberUtils.getNumberFromIntent(intent, mApp);  
  20.     if (VDBG) {  
  21.         log("- action: " + action);  
  22.         log("- uri: " + uri);  
  23.         log("- scheme: " + scheme);  
  24.         log("- number: " + number);  
  25.     }  
  26.   
  27.     if (!(Intent.ACTION_CALL.equals(action)  
  28.           || Intent.ACTION_CALL_EMERGENCY.equals(action)  
  29.           || Intent.ACTION_CALL_PRIVILEGED.equals(action))) {  
  30.         Log.wtf(TAG, "placeCall: unexpected intent action " + action);  
  31.         throw new IllegalArgumentException("Unexpected action: " + action);  
  32.     }  
  33.     // Check to see if this is an OTASP call (the "activation" call  
  34.     // used to provision CDMA devices), and if so, do some  
  35.     // OTASP-specific setup.  
  36.     Phone phone = mApp.mCM.getDefaultPhone();  
  37.     if (TelephonyCapabilities.supportsOtasp(phone)) {  
  38.         checkForOtaspCall(intent);  
  39.     }  
  40.     mApp.setRestoreMuteOnInCallResume(false);  
  41.     // If a provider is used, extract the info to build the  
  42.     // overlay and route the call.  The overlay will be  
  43.     // displayed when the InCallScreen becomes visible.  
  44.     if (PhoneUtils.hasPhoneProviderExtras(intent)) {  
  45.         inCallUiState.setProviderInfo(intent);  
  46.     } else {  
  47.         inCallUiState.clearProviderInfo();  
  48.     }  
  49.     //拨号  
  50.     CallStatusCode status = placeCallInternal(intent);  
  51.   
  52.     switch (status) {  
  53.         case SUCCESS:  
  54.         case EXITED_ECM:  
  55.             if (DBG) log("==> placeCall(): success from placeCallInternal(): " + status);  
  56.             if (status == CallStatusCode.EXITED_ECM) {  
  57.                 // Call succeeded, but we also need to tell the  
  58.                 // InCallScreen to show the "Exiting ECM" warning.  
  59.                 inCallUiState.setPendingCallStatusCode(CallStatusCode.EXITED_ECM);  
  60.             } else {  
  61.                 // Call succeeded.  There's no "error condition" that  
  62.                 // needs to be displayed to the user, so clear out the  
  63.                 // InCallUiState's "pending call status code".  
  64.                 inCallUiState.clearPendingCallStatusCode();  
  65.             }  
  66.   
  67.             // Notify the phone app that a call is beginning so it can  
  68.             // enable the proximity sensor  
  69.             mApp.setBeginningCall(true);  
  70.             break;  
  71.   
  72.         default:  
  73.             // Any other status code is a failure.  
  74.             log("==> placeCall(): failure code from placeCallInternal(): " + status);  
  75.             // Handle the various error conditions that can occur when  
  76.             // initiating an outgoing call, typically by directing the  
  77.             // InCallScreen to display a diagnostic message (via the  
  78.             // "pending call status code" flag.)  
  79.             handleOutgoingCallError(status);  
  80.             break;  
  81.     }  
  82.     mApp.displayCallScreen();  
  83. }  
该函数首先得到拨打的电话号码及默认的Phone对象,调用placeCallInternal发起拨号请求,同时启动电话呼叫界面InCallScreen。

1.拨号流程

[java] view plaincopy
  1. private CallStatusCode placeCallInternal(Intent intent) {  
  2.     final InCallUiState inCallUiState = mApp.inCallUiState;  
  3.     final Uri uri = intent.getData();  
  4.     final String scheme = (uri != null) ? uri.getScheme() : null;  
  5.     String number;  
  6.     Phone phone = null;  
  7.   
  8.     CallStatusCode okToCallStatus = checkIfOkToInitiateOutgoingCall(  
  9.             mCM.getServiceState());  
  10.     try {  
  11.         number = PhoneUtils.getInitialNumber(intent);  
  12.         if (VDBG) log("- actual number to dial: '" + number + "'");  
  13.         String sipPhoneUri = intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI);  
  14.         phone = PhoneUtils.pickPhoneBasedOnNumber(mCM, scheme, number, sipPhoneUri);  
  15.         if (VDBG) log("- got Phone instance: " + phone + ", class = " + phone.getClass());  
  16.         okToCallStatus = checkIfOkToInitiateOutgoingCall(phone.getServiceState().getState());  
  17.     } catch (PhoneUtils.VoiceMailNumberMissingException ex) {  
  18.         if (okToCallStatus != CallStatusCode.SUCCESS) {  
  19.             if (DBG) log("Voicemail number not reachable in current SIM card state.");  
  20.             return okToCallStatus;  
  21.         }  
  22.         if (DBG) log("VoiceMailNumberMissingException from getInitialNumber()");  
  23.         return CallStatusCode.VOICEMAIL_NUMBER_MISSING;  
  24.     }  
  25.   
  26.     if (number == null) {  
  27.         Log.w(TAG, "placeCall: couldn't get a phone number from Intent " + intent);  
  28.         return CallStatusCode.NO_PHONE_NUMBER_SUPPLIED;  
  29.     }  
  30.     boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number, mApp);  
  31.     boolean isPotentialEmergencyNumber =PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, mApp);  
  32.     boolean isEmergencyIntent = Intent.ACTION_CALL_EMERGENCY.equals(intent.getAction());  
  33.       
  34.     if (isPotentialEmergencyNumber && !isEmergencyIntent) {  
  35.         Log.e(TAG, "Non-CALL_EMERGENCY Intent " + intent+ " attempted to call potential emergency number " + number             + ".");  
  36.         return CallStatusCode.CALL_FAILED;  
  37.     } else if (!isPotentialEmergencyNumber && isEmergencyIntent) {  
  38.         Log.e(TAG, "Received CALL_EMERGENCY Intent " + intent  
  39.                 + " with non-potential-emergency number " + number  
  40.                 + " -- failing call.");  
  41.         return CallStatusCode.CALL_FAILED;  
  42.     }  
  43.     // If we're trying to call an emergency number, then it's OK to  
  44.     // proceed in certain states where we'd otherwise bring up  
  45.     // an error dialog:  
  46.     // - If we're in EMERGENCY_ONLY mode, then (obviously) you're allowed  
  47.     //   to dial emergency numbers.  
  48.     // - If we're OUT_OF_SERVICE, we still attempt to make a call,  
  49.     //   since the radio will register to any available network.  
  50.     if (isEmergencyNumber  
  51.         && ((okToCallStatus == CallStatusCode.EMERGENCY_ONLY)  
  52.             || (okToCallStatus == CallStatusCode.OUT_OF_SERVICE))) {  
  53.         if (DBG) log("placeCall: Emergency number detected with status = " + okToCallStatus);  
  54.         okToCallStatus = CallStatusCode.SUCCESS;  
  55.         if (DBG) log("==> UPDATING status to: " + okToCallStatus);  
  56.     }  
  57.   
  58.     if (okToCallStatus != CallStatusCode.SUCCESS) {  
  59.         // If this is an emergency call, launch the EmergencyCallHelperService  
  60.         // to turn on the radio and retry the call.  
  61.         if (isEmergencyNumber && (okToCallStatus == CallStatusCode.POWER_OFF)) {  
  62.             Log.i(TAG, "placeCall: Trying to make emergency call while POWER_OFF!");  
  63.             // If needed, lazily instantiate an EmergencyCallHelper instance.  
  64.             synchronized (this) {  
  65.                 if (mEmergencyCallHelper == null) {  
  66.                     mEmergencyCallHelper = new EmergencyCallHelper(this);  
  67.                 }  
  68.             }  
  69.             // ...and kick off the "emergency call from airplane mode" sequence.  
  70.             mEmergencyCallHelper.startEmergencyCallFromAirplaneModeSequence(number);  
  71.             return CallStatusCode.SUCCESS;  
  72.         } else {  
  73.             if (DBG) log("==> placeCallInternal(): non-success status: " + okToCallStatus);  
  74.             return okToCallStatus;  
  75.         }  
  76.     }  
  77.     // Ok, we can proceed with this outgoing call.  
  78.     inCallUiState.needToShowCallLostDialog = false;  
  79.     inCallUiState.clearProgressIndication();  
  80.     Uri contactUri = intent.getData();  
  81.     //真正的电话拨号过程  
  82.     int callStatus = PhoneUtils.placeCall(mApp,  
  83.                                           phone,  
  84.                                           number,  
  85.                                           contactUri,  
  86.                                           (isEmergencyNumber || isEmergencyIntent),  
  87.                                           inCallUiState.providerGatewayUri);  
  88.   
  89.     switch (callStatus) {  
  90.         case PhoneUtils.CALL_STATUS_DIALED:  
  91.             if (VDBG) log("placeCall: PhoneUtils.placeCall() succeeded for regular call '"  
  92.                          + number + "'.");  
  93.             if (VDBG) log ("- inCallUiState.inCallScreenMode = "  
  94.                            + inCallUiState.inCallScreenMode);  
  95.             if (inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL) {  
  96.                 if (VDBG) log ("==>  OTA_NORMAL note: switching to OTA_STATUS_LISTENING.");  
  97.                 mApp.cdmaOtaScreenState.otaScreenState =  
  98.                         CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING;  
  99.             }  
  100.   
  101.             boolean voicemailUriSpecified = scheme != null && scheme.equals("voicemail");  
  102.             // When voicemail is requested most likely the user wants to open  
  103.             // dialpad immediately, so we show it in the first place.  
  104.             // Otherwise we want to make sure the user can see the regular  
  105.             // in-call UI while the new call is dialing, and when it  
  106.             // first gets connected.)  
  107.             inCallUiState.showDialpad = voicemailUriSpecified;  
  108.   
  109.             // For voicemails, we add context text to let the user know they  
  110.             // are dialing their voicemail.  
  111.             // TODO: This is only set here and becomes problematic when swapping calls  
  112.             inCallUiState.dialpadContextText = voicemailUriSpecified ?  
  113.                 phone.getVoiceMailAlphaTag() : "";  
  114.   
  115.             // Also, in case a previous call was already active (i.e. if  
  116.             // we just did "Add call"), clear out the "history" of DTMF  
  117.             // digits you typed, to make sure it doesn't persist from the  
  118.             // previous call to the new call.  
  119.             // TODO: it would be more precise to do this when the actual  
  120.             // phone state change happens (i.e. when a new foreground  
  121.             // call appears and the previous call moves to the  
  122.             // background), but the InCallScreen doesn't keep enough  
  123.             // state right now to notice that specific transition in  
  124.             // onPhoneStateChanged().  
  125.             inCallUiState.dialpadDigits = null;  
  126.   
  127.             // Check for an obscure ECM-related scenario: If the phone  
  128.             // is currently in ECM (Emergency callback mode) and we  
  129.             // dial a non-emergency number, that automatically  
  130.             // *cancels* ECM.  So warn the user about it.  
  131.             // (See InCallScreen.showExitingECMDialog() for more info.)  
  132.             boolean exitedEcm = false;  
  133.             if (PhoneUtils.isPhoneInEcm(phone) && !isEmergencyNumber) {  
  134.                 Log.i(TAG, "About to exit ECM because of an outgoing non-emergency call");  
  135.                 exitedEcm = true;  // this will cause us to return EXITED_ECM from this method  
  136.             }  
  137.   
  138.             if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {  
  139.                 // Start the timer for 3 Way CallerInfo  
  140.                 if (mApp.cdmaPhoneCallState.getCurrentCallState()  
  141.                         == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {  
  142.                     //Unmute for the second MO call  
  143.                     PhoneUtils.setMute(false);  
  144.   
  145.                     // This is a "CDMA 3-way call", which means that you're dialing a  
  146.                     // 2nd outgoing call while a previous call is already in progress.  
  147.                     //  
  148.                     // Due to the limitations of CDMA this call doesn't actually go  
  149.                     // through the DIALING/ALERTING states, so we can't tell for sure  
  150.                     // when (or if) it's actually answered.  But we want to show  
  151.                     // *some* indication of what's going on in the UI, so we "fake it"  
  152.                     // by displaying the "Dialing" state for 3 seconds.  
  153.   
  154.                     // Set the mThreeWayCallOrigStateDialing state to true  
  155.                     mApp.cdmaPhoneCallState.setThreeWayCallOrigState(true);  
  156.   
  157.                     // Schedule the "Dialing" indication to be taken down in 3 seconds:  
  158.                     sendEmptyMessageDelayed(THREEWAY_CALLERINFO_DISPLAY_DONE,  
  159.                                             THREEWAY_CALLERINFO_DISPLAY_TIME);  
  160.                 }  
  161.             }  
  162.   
  163.             // Success!  
  164.             if (exitedEcm) {  
  165.                 return CallStatusCode.EXITED_ECM;  
  166.             } else {  
  167.                 return CallStatusCode.SUCCESS;  
  168.             }  
  169.   
  170.         case PhoneUtils.CALL_STATUS_DIALED_MMI:  
  171.             if (DBG) log("placeCall: specified number was an MMI code: '" + number + "'.");  
  172.             // The passed-in number was an MMI code, not a regular phone number!  
  173.             // This isn't really a failure; the Dialer may have deliberately  
  174.             // fired an ACTION_CALL intent to dial an MMI code, like for a  
  175.             // USSD call.  
  176.             //  
  177.             // Presumably an MMI_INITIATE message will come in shortly  
  178.             // (and we'll bring up the "MMI Started" dialog), or else  
  179.             // an MMI_COMPLETE will come in (which will take us to a  
  180.             // different Activity; see PhoneUtils.displayMMIComplete()).  
  181.             return CallStatusCode.DIALED_MMI;  
  182.   
  183.         case PhoneUtils.CALL_STATUS_FAILED:  
  184.             Log.w(TAG, "placeCall: PhoneUtils.placeCall() FAILED for number '"  
  185.                   + number + "'.");  
  186.             // We couldn't successfully place the call; there was some  
  187.             // failure in the telephony layer.  
  188.             return CallStatusCode.CALL_FAILED;  
  189.   
  190.         default:  
  191.             Log.wtf(TAG, "placeCall: unknown callStatus " + callStatus  
  192.                     + " from PhoneUtils.placeCall() for number '" + number + "'.");  
  193.             return CallStatusCode.SUCCESS;  // Try to continue anyway...  
  194.     }  
  195. }  
该函数通过调用PhoneUtils类的placeCall函数进入Framework层异步完成电话呼叫

src\com\android\phone\PhoneUtils.java

[java] view plaincopy
  1. public static int placeCall(Context context, Phone phone,  
  2.         String number, Uri contactRef, boolean isEmergencyCall,  
  3.         Uri gatewayUri) {  
  4.     final PhoneGlobals app = PhoneGlobals.getInstance();  
  5.     boolean useGateway = false;  
  6.     if (null != gatewayUri &&  
  7.         !isEmergencyCall &&  
  8.         PhoneUtils.isRoutableViaGateway(number)) {  // Filter out MMI, OTA and other codes.  
  9.         useGateway = true;  
  10.     }  
  11.   
  12.     int status = CALL_STATUS_DIALED;  
  13.     Connection connection;  
  14.     String numberToDial;  
  15.     if (useGateway) {  
  16.         if (null == gatewayUri || !Constants.SCHEME_TEL.equals(gatewayUri.getScheme())) {  
  17.             Log.e(LOG_TAG, "Unsupported URL:" + gatewayUri);  
  18.             return CALL_STATUS_FAILED;  
  19.         }  
  20.         // We can use getSchemeSpecificPart because we don't allow #  
  21.         // in the gateway numbers (treated a fragment delim.) However  
  22.         // if we allow more complex gateway numbers sequence (with  
  23.         // passwords or whatnot) that use #, this may break.  
  24.         // TODO: Need to support MMI codes.  
  25.         numberToDial = gatewayUri.getSchemeSpecificPart();  
  26.     } else {  
  27.         numberToDial = number;  
  28.     }  
  29.   
  30.     // Remember if the phone state was in IDLE state before this call.  
  31.     // After calling CallManager#dial(), getState() will return different state.  
  32.     final boolean initiallyIdle = app.mCM.getState() == PhoneConstants.State.IDLE;  
  33.   
  34.     try {  
  35.         connection = app.mCM.dial(phone, numberToDial);  
  36.     } catch (CallStateException ex) {  
  37.         // CallStateException means a new outgoing call is not currently  
  38.         // possible: either no more call slots exist, or there's another  
  39.         // call already in the process of dialing or ringing.  
  40.         Log.w(LOG_TAG, "Exception from app.mCM.dial()", ex);  
  41.         return CALL_STATUS_FAILED;  
  42.   
  43.         // Note that it's possible for CallManager.dial() to return  
  44.         // null *without* throwing an exception; that indicates that  
  45.         // we dialed an MMI (see below).  
  46.     }  
  47.   
  48.     int phoneType = phone.getPhoneType();  
  49.   
  50.     // On GSM phones, null is returned for MMI codes  
  51.     if (null == connection) {  
  52.         if (phoneType == PhoneConstants.PHONE_TYPE_GSM && gatewayUri == null) {  
  53.             if (DBG) log("dialed MMI code: " + number);  
  54.             status = CALL_STATUS_DIALED_MMI;  
  55.         } else {  
  56.             status = CALL_STATUS_FAILED;  
  57.         }  
  58.     } else {  
  59.         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {  
  60.             updateCdmaCallStateOnNewOutgoingCall(app);  
  61.         }  
  62.   
  63.         // Clean up the number to be displayed.  
  64.         if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {  
  65.             number = CdmaConnection.formatDialString(number);  
  66.         }  
  67.         number = PhoneNumberUtils.extractNetworkPortion(number);  
  68.         number = PhoneNumberUtils.convertKeypadLettersToDigits(number);  
  69.         number = PhoneNumberUtils.formatNumber(number);  
  70.   
  71.         if (gatewayUri == null) {  
  72.             // phone.dial() succeeded: we're now in a normal phone call.  
  73.             // attach the URI to the CallerInfo Object if it is there,  
  74.             // otherwise just attach the Uri Reference.  
  75.             // if the uri does not have a "content" scheme, then we treat  
  76.             // it as if it does NOT have a unique reference.  
  77.             String content = context.getContentResolver().SCHEME_CONTENT;  
  78.             if ((contactRef != null) && (contactRef.getScheme().equals(content))) {  
  79.                 Object userDataObject = connection.getUserData();  
  80.                 if (userDataObject == null) {  
  81.                     connection.setUserData(contactRef);  
  82.                 } else {  
  83.                     // TODO: This branch is dead code, we have  
  84.                     // just created the connection which has  
  85.                     // no user data (null) by default.  
  86.                     if (userDataObject instanceof CallerInfo) {  
  87.                     ((CallerInfo) userDataObject).contactRefUri = contactRef;  
  88.                     } else {  
  89.                     ((CallerInfoToken) userDataObject).currentInfo.contactRefUri =  
  90.                         contactRef;  
  91.                     }  
  92.                 }  
  93.             }  
  94.         } else {  
  95.             // Get the caller info synchronously because we need the final  
  96.             // CallerInfo object to update the dialed number with the one  
  97.             // requested by the user (and not the provider's gateway number).  
  98.             CallerInfo info = null;  
  99.             String content = phone.getContext().getContentResolver().SCHEME_CONTENT;  
  100.             if ((contactRef != null) && (contactRef.getScheme().equals(content))) {  
  101.                 info = CallerInfo.getCallerInfo(context, contactRef);  
  102.             }  
  103.   
  104.             // Fallback, lookup contact using the phone number if the  
  105.             // contact's URI scheme was not content:// or if is was but  
  106.             // the lookup failed.  
  107.             if (null == info) {  
  108.                 info = CallerInfo.getCallerInfo(context, number);  
  109.             }  
  110.             info.phoneNumber = number;  
  111.             connection.setUserData(info);  
  112.         }  
  113.         setAudioMode();  
  114.   
  115.         if (DBG) log("about to activate speaker");  
  116.         // Check is phone in any dock, and turn on speaker accordingly  
  117.         final boolean speakerActivated = activateSpeakerIfDocked(phone);  
  118.   
  119.         // See also similar logic in answerCall().  
  120.         if (initiallyIdle && !speakerActivated && isSpeakerOn(app)  
  121.                 && !app.isBluetoothHeadsetAudioOn()) {  
  122.             // This is not an error but might cause users' confusion. Add log just in case.  
  123.             Log.i(LOG_TAG, "Forcing speaker off when initiating a new outgoing call...");  
  124.             PhoneUtils.turnOnSpeaker(app, falsetrue);  
  125.         }  
  126.     }  
  127.     return status;  
  128. }  
该函数调用framework层的CallManager的dial函数。
[java] view plaincopy
  1. public Connection dial(Phone phone, String dialString) throws CallStateException {  
  2.     Phone basePhone = getPhoneBase(phone);  
  3.     Connection result;  
  4.     if (VDBG) {  
  5.         Log.d(LOG_TAG, " dial(" + basePhone + ", "+ dialString + ")");  
  6.         Log.d(LOG_TAG, this.toString());  
  7.     }  
  8.     if (!canDial(phone)) {  
  9.         throw new CallStateException("cannot dial in current state");  
  10.     }  
  11.     if (hasActiveFgCall() ) {  
  12.         Phone activePhone = getActiveFgCall().getPhone();  
  13.         boolean hasBgCall = !(activePhone.getBackgroundCall().isIdle());  
  14.         if (DBG) {  
  15.             Log.d(LOG_TAG, "hasBgCall: "+ hasBgCall + " sameChannel:" + (activePhone == basePhone));  
  16.         }  
  17.         if (activePhone != basePhone) {  
  18.             if (hasBgCall) {  
  19.                 Log.d(LOG_TAG, "Hangup");  
  20.                 getActiveFgCall().hangup();  
  21.             } else {  
  22.                 Log.d(LOG_TAG, "Switch");  
  23.                 activePhone.switchHoldingAndActive();  
  24.             }  
  25.         }  
  26.     }  
  27.     result = basePhone.dial(dialString);  
  28.     if (VDBG) {  
  29.         Log.d(LOG_TAG, "End dial(" + basePhone + ", "+ dialString + ")");  
  30.         Log.d(LOG_TAG, this.toString());  
  31.     }  
  32.     return result;  
  33. }  
函数首先取得相应类型的Phone,并判断该Phone的状态,这里得到PhoneProxy类型的Phone,PhoneProxy是所有类型Phone的代理类,在构造PhoneProxy时,把对应类型的Phone保存在其成员变量mActivePhone中,有关Phone,PhoneProxy,GmsPhone,CDMAPhone之间的关系请参看Android电话Phone设计框架介绍,GSM类型的网络对应GSMPhone,因此这里将调用GSMPhone类的dial函数。

./telephony/java/com/android/internal/telephony/gsm/GSMPhone.java

[java] view plaincopy
  1. public Connection dial(String dialString) throws CallStateException {  
  2.     return dial(dialString, null);  
  3. }  

[java] view plaincopy
  1. public Connection dial (String dialString, UUSInfo uusInfo) throws CallStateException {  
  2.     // Need to make sure dialString gets parsed properly  
  3.     String newDialString = PhoneNumberUtils.stripSeparators(dialString);  
  4.     // handle in-call MMI first if applicable  
  5.     if (handleInCallMmiCommands(newDialString)) {  
  6.         return null;  
  7.     }  
  8.     // Only look at the Network portion for mmi  
  9.     String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);  
  10.     GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this);  
  11.     if (mmi == null) {  
  12.         return mCT.dial(newDialString, uusInfo);  
  13.     } else if (mmi.isTemporaryModeCLIR()) {  
  14.         return mCT.dial(mmi.dialingNumber, mmi.getCLIRMode(), uusInfo);  
  15.     } else {  
  16.         mPendingMMIs.add(mmi);  
  17.         mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));  
  18.         mmi.processCode();  
  19.         // FIXME should this return null or something else?  
  20.         return null;          
  21.     }  
  22. }  
GSMPhone又通过CallTracker来向RIL发送请求,关于CallTracker的类关系请参阅Android电话Phone设计框架介绍

./telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java

[java] view plaincopy
  1. synchronized Connection dial (String dialString, int clirMode, UUSInfo uusInfo) throws CallStateException {  
  2.     // note that this triggers call state changed notif  
  3.     clearDisconnected();  
  4.   
  5.     if (!canDial()) {  
  6.         throw new CallStateException("cannot dial in current state");  
  7.     }  
  8.   
  9.     // The new call must be assigned to the foreground call.  
  10.     // That call must be idle, so place anything that's  
  11.     // there on hold  
  12.     if (foregroundCall.getState() == GsmCall.State.ACTIVE) {  
  13.         // this will probably be done by the radio anyway  
  14.         // but the dial might fail before this happens  
  15.         // and we need to make sure the foreground call is clear  
  16.         // for the newly dialed connection  
  17.         switchWaitingOrHoldingAndActive();  
  18.   
  19.         // Fake local state so that  
  20.         // a) foregroundCall is empty for the newly dialed connection  
  21.         // b) hasNonHangupStateChanged remains false in the  
  22.         // next poll, so that we don't clear a failed dialing call  
  23.         fakeHoldForegroundBeforeDial();  
  24.     }  
  25.   
  26.     if (foregroundCall.getState() != GsmCall.State.IDLE) {  
  27.         //we should have failed in !canDial() above before we get here  
  28.         throw new CallStateException("cannot dial in current state");  
  29.     }  
  30.   
  31. //        pendingMO = new GsmConnection(phone.getContext(), checkForTestEmergencyNumber(dialString),  
  32. //                this, foregroundCall);  
  33.     boolean isStkCall = getStkCall();  
  34.     log("GsmCallTracker dial: isStkCall=" + isStkCall);  
  35.     pendingMO = new GsmConnection(phone.getContext(), dialString, this, foregroundCall, isStkCall, false);  
  36.     hangupPendingMO = false;  
  37.   
  38.     if (pendingMO.address == null || pendingMO.address.length() == 0  
  39.         || pendingMO.address.indexOf(PhoneNumberUtils.WILD) >= 0  
  40.     ) {  
  41.         // Phone number is invalid  
  42.         pendingMO.cause = Connection.DisconnectCause.INVALID_NUMBER;  
  43.   
  44.         // handlePollCalls() will notice this call not present  
  45.         // and will mark it as dropped.  
  46.         pollCallsWhenSafe();  
  47.     } else {  
  48.         // Always unmute when initiating a new call  
  49.         setMute(false);  
  50.   
  51. //            cm.dial(pendingMO.address, clirMode, uusInfo, obtainCompleteMessage());  
  52.         // Add for bug 121825 Start  
  53.         String tmpAddr = pendingMO.address;  
  54.         if (PhoneNumberUtils.isCustomEmergencyNumber(pendingMO.address)) {  
  55.             Log.d(LOG_TAG,"Pending MO is Custom Emergency call");  
  56.             tmpAddr = tmpAddr + "/1";  
  57.         }  
  58.         //cm.dial(pendingMO.address, clirMode, uusInfo, isStkCall, obtainCompleteMessage());  
  59.         cm.dial(tmpAddr, clirMode, uusInfo, isStkCall, obtainCompleteMessage());  
  60.         // Add for bug 121825 End  
  61.     }  
  62.   
  63.     updatePhoneState();  
  64.     phone.notifyPreciseCallStateChanged();  
  65.   
  66.     return pendingMO;  
  67. }  
cm的类型为CommandsInterface,RIL.java实现了CommandsInterface接口,因此GsmCallTracker最终是通过RIL来发送拨号请求的。

./telephony/java/com/android/internal/telephony/RIL.java

[java] view plaincopy
  1. public void dial(String address, int clirMode, UUSInfo uusInfo, boolean isStkCall, Message result) {  
  2.     RILRequest rr;  
  3.   
  4.     if (address.indexOf('/') == -1) {  
  5.         rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);  
  6.     } else {  
  7.         rr = RILRequest.obtain(RIL_REQUEST_DIAL_EMERGENCY_CALL, result);  
  8.     }  
  9.     rr.mp.writeString(address);  
  10.     rr.mp.writeInt(clirMode);  
  11.     rr.mp.writeInt(0); // UUS information is absent  
  12.     if (uusInfo == null) {  
  13.         rr.mp.writeInt(0); // UUS information is absent  
  14.     } else {  
  15.         rr.mp.writeInt(1); // UUS information is present  
  16.         rr.mp.writeInt(uusInfo.getType());  
  17.         rr.mp.writeInt(uusInfo.getDcs());  
  18.         rr.mp.writeByteArray(uusInfo.getUserData());  
  19.     }  
  20.     rr.mp.writeInt(isStkCall ? 1:0);  
  21.   
  22.     if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));  
  23.   
  24.     send(rr);  
  25. }  

 Java部分的request请求号需要与C/C++部分的请求号保持一致。当需要执行某种AT命令request请求时,则需创建一个新的RILRequest,使用RILRequest的obtain函数,该obtain静态函数用于从其内部维护的一个 RIL request池sPool中取下一个request,得到一个RILRequest对象,它里面的请求号和用于回送结果及处理者handler的消息来自传递的实参。当一个RILRequest对象不再使用时,调用release() 函数将其释放回池中。将RILRequest请求放置到消息队列上,然后sender线程将其写入socket,rild侧通过dispatch线程将请求分发出去。在RIL类中,还维护了一个RILRequest请求列表,RILRequest类中的serial作为其id标识。当sender发送一个RIL请求后,则将其添加到该列表中,若发送时出现异常则需再清除;当请求完成并得到回送的response消息后,使用findAndRemoveRequestFromList函数将其移除。RIL请求执行AT是一个异步的过程:调用者调用RIL类的API函数只是往线程的消息队列上添加了一消息就返回;然后线程在执行无限循环时将其写到socket中,并将RILRequest对象添加到一个列表中;当RILReciever线程收到数据并解析,然后查询系列号后得到这是某个先前的RIL请求后,将AT执行的返回结果放到AsynResult中并赋值给Message中的obj成员后,由Message.sendToTarget送回到调用者并由其处理。


[java] view plaincopy
  1. protected void send(RILRequest rr) {  
  2.     Message msg;  
  3.     if (mSocket == null) {  
  4.         rr.onError(RADIO_NOT_AVAILABLE, null);  
  5.         rr.release();  
  6.         return;  
  7.     }  
  8.     msg = mSender.obtainMessage(EVENT_SEND, rr);  
  9.     acquireWakeLock();  
  10.     msg.sendToTarget();  
  11. }  
RILSender消息处理过程
[java] view plaincopy
  1. class RILSender extends Handler implements Runnable {  
  2.     public RILSender(Looper looper) {  
  3.         super(looper);  
  4.     }  
  5.     byte[] dataLength = new byte[4];  
  6.     public void  
  7.     run() {  
  8.         //setup if needed  
  9.     }  
  10.       
  11.     @Override public void  
  12.     handleMessage(Message msg) {  
  13.         RILRequest rr = (RILRequest)(msg.obj);  
  14.         RILRequest req = null;  
  15.         switch (msg.what) {  
  16.             case EVENT_SEND:  
  17.                 /** 
  18.                  * mRequestMessagePending++ already happened for every 
  19.                  * EVENT_SEND, thus we must make sure 
  20.                  * mRequestMessagePending-- happens once and only once 
  21.                  */  
  22.                 boolean alreadySubtracted = false;  
  23.                 try {  
  24.                     LocalSocket s;  
  25.                     s = mSocket;  
  26.                     if (s == null) {  
  27.                         rr.onError(RADIO_NOT_AVAILABLE, null);  
  28.                         rr.release();  
  29.                         if (mRequestMessagesPending > 0)  
  30.                             mRequestMessagesPending--;  
  31.                         alreadySubtracted = true;  
  32.                         return;  
  33.                     }  
  34.                     synchronized (mRequestsList) {  
  35.                         mRequestsList.add(rr);  
  36.                         mRequestMessagesWaiting++;  
  37.                     }  
  38.                     if (mRequestMessagesPending > 0)  
  39.                         mRequestMessagesPending--;  
  40.                     alreadySubtracted = true;  
  41.                     byte[] data;  
  42.                     data = rr.mp.marshall();  
  43.                     rr.mp.recycle();  
  44.                     rr.mp = null;  
  45.                     if (data.length > RIL_MAX_COMMAND_BYTES) {  
  46.                         throw new RuntimeException("Parcel larger than max bytes allowed! "+ data.length);  
  47.                     }  
  48.                     // parcel length in big endian  
  49.                     dataLength[0] = dataLength[1] = 0;  
  50.                     dataLength[2] = (byte)((data.length >> 8) & 0xff);  
  51.                     dataLength[3] = (byte)((data.length) & 0xff);  
  52.                     //Log.v(LOG_TAG, "writing packet: " + data.length + " bytes");  
  53.                     s.getOutputStream().write(dataLength);  
  54.                     s.getOutputStream().write(data);  
  55.                 } catch (IOException ex) {  
  56.                     Log.e(LOG_TAG, "IOException", ex);  
  57.                     req = findAndRemoveRequestFromList(rr.mSerial);  
  58.                     // make sure this request has not already been handled,  
  59.                     // eg, if RILReceiver cleared the list.  
  60.                     if (req != null || !alreadySubtracted) {  
  61.                         rr.onError(RADIO_NOT_AVAILABLE, null);  
  62.                         rr.release();  
  63.                     }  
  64.                 } catch (RuntimeException exc) {  
  65.                     Log.e(LOG_TAG, "Uncaught exception ", exc);  
  66.                     req = findAndRemoveRequestFromList(rr.mSerial);  
  67.                     // make sure this request has not already been handled,  
  68.                     // eg, if RILReceiver cleared the list.  
  69.                     if (req != null || !alreadySubtracted) {  
  70.                         rr.onError(GENERIC_FAILURE, null);  
  71.                         rr.release();  
  72.                     }  
  73.                 } finally {  
  74.                     // Note: We are "Done" only if there are no outstanding  
  75.                     // requests or replies. Thus this code path will only release  
  76.                     // the wake lock on errors.  
  77.                     releaseWakeLockIfDone();  
  78.                 }  
  79.                 if (!alreadySubtracted && mRequestMessagesPending > 0) {  
  80.                     mRequestMessagesPending--;  
  81.                 }  
  82.                 break;  
  83.         }  
  84.     }  
  85. }  
在处理EVENT_SEND消息时,将请求参数写入到rild套接字中,在Android电话Phone设计框架介绍中介绍了rild服务进程作为Android电话系统的服务端,接收客户端framework层发送过来的请求,并与modem交互,实现整个拨号过程。关于rild服务在Android之rild进程启动源码分析已经详细介绍了,至此电话拨号请求的发送过程就完成了。拨号的本质就是应用层Phone进程首先对拨打的号码进行一系列处理,然后进入framework层,通过framework层的电话客户端发送线程将请求通过套接字的方式发送给电话服务进程rild,rild将该请求映射为相应的AT指令发送到modem中。


2.拨号界面显示

在CallController的placeCall函数中,首先将拨号请求发送到rild服务进程,然后启动呼叫界面InCallScreen,Android电话Phone UI分析对InCallScreen的UI布局进行了详细的分析,只有了解InCallScreen的UI布局,才能更好地理解InCallScreen的启动过程。InCallScreen主要是显示通话界面, 并且还负责菜单项各种按键事件和触摸时间的处理。同时本类还复写的finish()方法,所以一般不会被finish掉,调用该方法时它又把自己放回栈中。

src\com\android\phone\PhoneGlobals.java

[java] view plaincopy
  1. void displayCallScreen() {  
  2.     if (VDBG) Log.d(LOG_TAG, "displayCallScreen()...");  
  3.     // On non-voice-capable devices we shouldn't ever be trying to  
  4.     // bring up the InCallScreen in the first place.  
  5.     if (!sVoiceCapable) {  
  6.         Log.w(LOG_TAG, "displayCallScreen() not allowed: non-voice-capable device",new Throwable("stack dump"));    
  7.         return;  
  8.     }  
  9.     //启动电话呼叫界面InCallScreen  
  10.     try {  
  11.         startActivity(createInCallIntent());  
  12.     } catch (ActivityNotFoundException e) {  
  13.         Log.w(LOG_TAG, "displayCallScreen: transition to InCallScreen failed: " + e);  
  14.     }  
  15.     Profiler.callScreenRequested();  
  16. }  


[java] view plaincopy
  1. static Intent createInCallIntent() {  
  2.     Intent intent = new Intent(Intent.ACTION_MAIN, null);  
  3.     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK  
  4.             | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS  
  5.             | Intent.FLAG_ACTIVITY_NO_USER_ACTION);  
  6.     intent.setClassName("com.android.phone", getCallScreenClassName());  
  7.     return intent;  
  8. }  
  9.   
  10. private static String getCallScreenClassName() {  
  11.     return InCallScreen.class.getName();  
  12. }  

第一次启动InCallScreen,首先调用其onCreate函数

src\com\android\phone\InCallScreen.java
[java] view plaincopy
  1. protected void onCreate(Bundle icicle) {  
  2.     Log.i(LOG_TAG, "onCreate()...  this = " + this);  
  3.     //获得通话界面被创建的时间  
  4.     Profiler.callScreenOnCreate();  
  5.     super.onCreate(icicle);  
  6.     // Make sure this is a voice-capable device.  
  7.     if (!PhoneGlobals.sVoiceCapable) {  
  8.         Log.wtf(LOG_TAG, "onCreate() reached on non-voice-capable device");  
  9.         finish();  
  10.         return;  
  11.     }  
  12.     mApp = PhoneGlobals.getInstance();  
  13.     mApp.setInCallScreenInstance(this);  
  14.     // set this flag so this activity will stay in front of the keyguard  
  15.     int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED  
  16.             | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;  
  17.     if (mApp.getPhoneState() == PhoneConstants.State.OFFHOOK) {  
  18.         flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;  
  19.     }  
  20.     WindowManager.LayoutParams lp = getWindow().getAttributes();  
  21.     lp.flags |= flags;  
  22.     if (!mApp.proximitySensorModeEnabled()) {  
  23.         // If we don't have a proximity sensor, then the in-call screen explicitly  
  24.         // controls user activity.  This is to prevent spurious touches from waking  
  25.         // the display.  
  26.         lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;  
  27.     }  
  28.     //设置窗体属性  
  29.     getWindow().setAttributes(lp);  
  30.     setPhone(mApp.phone);  // Sets mPhone  
  31.     mCM =  mApp.mCM;  
  32.     log("- onCreate: phone state = " + mCM.getState());  
  33.     mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();  
  34.     if (mBluetoothAdapter != null) {  
  35.         mBluetoothAdapter.getProfileProxy(getApplicationContext(), mBluetoothProfileServiceListener,  
  36.                                 BluetoothProfile.HEADSET);  
  37.     }  
  38.     //设置窗体显示风格  
  39.     requestWindowFeature(Window.FEATURE_NO_TITLE);  
  40.     //加载布局文件  
  41.     setContentView(R.layout.incall_screen);  
  42.     final ViewStub touchUiStub = (ViewStub) findViewById(  
  43.             mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA  
  44.             ? R.id.inCallTouchUiCdmaStub : R.id.inCallTouchUiStub);  
  45.     if (touchUiStub != null) touchUiStub.inflate();  
  46.     //加载各种view组建  
  47.     initInCallScreen();  
  48.     //对通话的各种状态进行广播。  
  49.     registerForPhoneStates();  
  50.     //判断是否使用了OTA技术,通过该判断设置通话界面的样式。  
  51.     if (icicle == null) {  
  52.         if (DBG) log("onCreate(): this is our very first launch, checking intent...");  
  53.         internalResolveIntent(getIntent());  
  54.     }  
  55.     //记录通话界面创建完成后的时间  
  56.     Profiler.callScreenCreated();  
  57.     if (DBG) log("onCreate(): exit");  
  58. }  
UI初始化过程,为了能更好的理解UI初始化过程,请查看Android电话Phone UI分析一文了解电话呼叫界面的UI布局。
[java] view plaincopy
  1. private void initInCallScreen() {  
  2.     if (VDBG) log("initInCallScreen()...");  
  3.     // Have the WindowManager filter out touch events that are "too fat".  
  4.     getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);  
  5.     // Initialize the CallCard.  
  6.     mCallCard = (CallCard) findViewById(R.id.callCard);  
  7.     if (VDBG) log("  - mCallCard = " + mCallCard);  
  8.     mCallCard.setInCallScreenInstance(this);  
  9.     // Initialize the onscreen UI elements.  
  10.     initInCallTouchUi();  
  11.     // Helper class to keep track of enabledness/state of UI controls  
  12.     mInCallControlState = new InCallControlState(this, mCM);  
  13.     // Helper class to run the "Manage conference" UI  
  14.     mManageConferenceUtils = new ManageConferenceUtils(this, mCM);  
  15.     // The DTMF Dialpad.  
  16.     ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);  
  17.     mDialer = new DTMFTwelveKeyDialer(this, stub);  
  18.     mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);  
  19. }  

[java] view plaincopy
  1. private void internalResolveIntent(Intent intent) {  
  2.     if (intent == null || intent.getAction() == null) {  
  3.         return;  
  4.     }  
  5.     String action = intent.getAction();  
  6.     if (DBG) log("internalResolveIntent: action=" + action);  
  7.     if (action.equals(intent.ACTION_MAIN)) {  
  8.         if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {  
  9.             boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);  
  10.             if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);  
  11.             mApp.inCallUiState.showDialpad = showDialpad;  
  12.             final boolean hasActiveCall = mCM.hasActiveFgCall();  
  13.             final boolean hasHoldingCall = mCM.hasActiveBgCall();  
  14.             if (showDialpad && !hasActiveCall && hasHoldingCall) {  
  15.                 PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());  
  16.             }  
  17.         }  
  18.         return;  
  19.     }  
  20.     if (action.equals(OtaUtils.ACTION_DISPLAY_ACTIVATION_SCREEN)) {  
  21.         if (!TelephonyCapabilities.supportsOtasp(mPhone)) {  
  22.             throw new IllegalStateException(  
  23.                 "Received ACTION_DISPLAY_ACTIVATION_SCREEN intent on non-OTASP-capable device: "  
  24.                 + intent);  
  25.         }  
  26.         setInCallScreenMode(InCallScreenMode.OTA_NORMAL);  
  27.         if ((mApp.cdmaOtaProvisionData != null)  
  28.             && (!mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {  
  29.             mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;  
  30.             mApp.cdmaOtaScreenState.otaScreenState =  
  31.                     CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;  
  32.         }  
  33.         return;  
  34.     }  
  35.     if (action.equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {  
  36.         throw new IllegalStateException(  
  37.             "Unexpected ACTION_PERFORM_CDMA_PROVISIONING received by InCallScreen: "+ intent);  
  38.     } else if (action.equals(Intent.ACTION_CALL) || action.equals(Intent.ACTION_CALL_EMERGENCY)) {  
  39.         // ACTION_CALL* intents go to the OutgoingCallBroadcaster, which now  
  40.         // translates them into CallController.placeCall() calls rather than  
  41.         // launching the InCallScreen directly.  
  42.         throw new IllegalStateException("Unexpected CALL action received by InCallScreen: "+ intent);  
  43.     } else if (action.equals(ACTION_UNDEFINED)) {  
  44.         // This action is only used for internal bookkeeping; we should  
  45.         // never actually get launched with it.  
  46.         Log.wtf(LOG_TAG, "internalResolveIntent: got launched with ACTION_UNDEFINED");  
  47.         return;  
  48.     } else {  
  49.         Log.wtf(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);  
  50.         // But continue the best we can (basically treating this case  
  51.         // like ACTION_MAIN...)  
  52.         return;  
  53.     }  
  54. }  
快速回复TOP
0 0
原创粉丝点击