Android电话拨打流程源码分析

来源:互联网 发布:用友网络2017最新消息 编辑:程序博客网 时间:2024/04/30 08:31

前面分析了电话拨号界面及电话呼叫界面,由于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. ... 

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

[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.     } 

调用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.     } 

函数首先对输入的号码进行检查,如果没有输入号码,直接按下拨号按钮,则调用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.     } 

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

DialtactsActivity.java

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

从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. public static Intent getCallIntent(Uri uri, String callOrigin) { 
  5.     final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri); 
  6.     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
  7.     if (callOrigin != null) { 
  8.         intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN, callOrigin); 
  9.     } 
  10.     return intent; 

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()); 

函数直接调用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 
首先获取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(); 
直接调用函数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); 

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); 
电话类型选择处理: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. //取出原始电话拨号intent的副本 
  11. mIntent = (Intent) intent.getParcelableExtra(OutgoingCallBroadcaster.EXTRA_NEW_CALL_INTENT); 
  12. if (mIntent == null) { 
  13.     finish(); 
  14.     return
  15. // Allow this activity to be visible in front of the keyguard. 
  16. getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); 
  17. // - If it's a sip: URI, this is definitely a SIP call, regardless 
  18. //   of whether the data is a SIP address or a regular phone 
  19. //   number. 
  20. // - If this is a tel: URI but the data contains an "@" character 
  21. //   (see PhoneNumberUtils.isUriNumber()) we consider that to be a 
  22. //   SIP number too. 
  23. boolean voipSupported = PhoneUtils.isVoipSupported(); 
  24. if (DBG) Log.v(TAG, "voipSupported: " + voipSupported); 
  25. mSipProfileDb = new SipProfileDb(this); 
  26. mSipSharedPreferences = new SipSharedPreferences(this); 
  27. mCallOption = mSipSharedPreferences.getSipCallOption(); 
  28. if (DBG) Log.v(TAG, "Call option: " + mCallOption); 
  29. Uri uri = mIntent.getData(); 
  30. String scheme = uri.getScheme(); 
  31. mNumber = PhoneNumberUtils.getNumberFromIntent(mIntent, this); 
  32. boolean isInCellNetwork = PhoneGlobals.getInstance().phoneMgr.isRadioOn(); 
  33. boolean isKnownCallScheme = Constants.SCHEME_TEL.equals(scheme) 
  34.         || Constants.SCHEME_SIP.equals(scheme); 
  35. boolean isRegularCall = Constants.SCHEME_TEL.equals(scheme) 
  36.         && !PhoneNumberUtils.isUriNumber(mNumber); 
  37. // Bypass the handler if the call scheme is not sip or tel. 
  38. if (!isKnownCallScheme) { 
  39.     setResultAndFinish(); 
  40.     return
  41. // Check if VoIP feature is supported. 
  42. if (!voipSupported) { 
  43.     if (!isRegularCall) { 
  44.         showDialog(DIALOG_NO_VOIP); 
  45.     } else
  46.         setResultAndFinish(); 
  47.     } 
  48.     return
  49. if (!PhoneUtils.hasPhoneProviderExtras(mIntent)) { 
  50.     if (!isNetworkConnected()) { 
  51.         if (!isRegularCall) { 
  52.             //显示无网络错误提示对话框 
  53.             showDialog(DIALOG_NO_INTERNET_ERROR); 
  54.             return
  55.         } 
  56.     } else
  57.         if (mCallOption.equals(Settings.System.SIP_ASK_ME_EACH_TIME) 
  58.                 && isRegularCall && isInCellNetwork) { 
  59.             //显示电话类型选择对话框 
  60.             showDialog(DIALOG_SELECT_PHONE_TYPE); 
  61.             return
  62.         } 
  63.         if (!mCallOption.equals(Settings.System.SIP_ADDRESS_ONLY) 
  64.                 || !isRegularCall) { 
  65.             mUseSipPhone = true
  66.         } 
  67.     } 
  68. if (mUseSipPhone) { 
  69.     // If there is no sip profile and it is a regular call, then we 
  70.     // should use pstn network instead. 
  71.     if ((mSipProfileDb.getProfilesCount() >0) || !isRegularCall) { 
  72.         startGetPrimarySipPhoneThread(); 
  73.         return
  74.     } else
  75.         mUseSipPhone = false
  76.     } 
  77. 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.     }); 
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(); 
该函数首先得到拨打的电话号码及默认的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.     } 
该函数通过调用PhoneUtils类的placeCall函数进入Framework层异步完成电话呼叫

src\com\android\phone\PhoneUtils.java

[java] view plaincopy
  1. public staticint 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 (userDataObjectinstanceof 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, false,true); 
  125.         } 
  126.     } 
  127.     return status; 
该函数调用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; 
函数首先取得相应类型的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); 

[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.     } 
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; 
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); 

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(); 
RILSender消息处理过程
[java] view plaincopy
  1. class RILSender extends Handlerimplements 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.     } 
在处理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(); 


[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. private static String getCallScreenClassName() { 
  10.     return InCallScreen.class.getName(); 

第一次启动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"); 
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); 

[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.     } 
更多1
0 0
原创粉丝点击