Android4.0(Phone)拨号启动过程分析

得意犹堪夸世俗,诏黄新湿字如鸦。这篇文章主要讲述Android4.0(Phone)拨号启动过程分析相关的知识,希望能为你提供帮助。
      因为工作的须要。须要改动原生的Phone程序,如今就好好看下来电与拨号是怎样处理的;无论是拨号还是来电,调用的都是Phone程序,因为非常多类都涉及到framework层,比較复杂;先从简单的拨号分析。在外部拨号是由Action:android.intent.action.CALL_PRIVILEGED或android.intent.action.CALL发起,这里仅仅分析android.intent.action.CALL的情况,程序文件夹结构:

Android4.0(Phone)拨号启动过程分析

文章图片


能够在Phone程序的AndroidManifest.xml文件里找到

< activity android:name=" OutgoingCallBroadcaster" android:configChanges=" orientation|screenSize|keyboardHidden" android:permission=" android.permission.CALL_PHONE" android:theme=" @android:style/Theme.NoDisplay" > < !-- CALL action intent filters, for the various ways of initiating an outgoing call. --> < intent-filter> < action android:name=" android.intent.action.CALL" /> < category android:name=" android.intent.category.DEFAULT" /> < data android:scheme=" tel" /> < /intent-filter> < intent-filter android:icon=" @drawable/ic_launcher_sip_call" > < action android:name=" android.intent.action.CALL" /> < category android:name=" android.intent.category.DEFAULT" /> < data android:scheme=" sip" /> < /intent-filter> < intent-filter> < action android:name=" android.intent.action.CALL" /> < category android:name=" android.intent.category.DEFAULT" /> < data android:scheme=" voicemail" /> < /intent-filter> < intent-filter> < action android:name=" android.intent.action.CALL" /> < category android:name=" android.intent.category.DEFAULT" /> < data android:mimeType=" vnd.android.cursor.item/phone" /> < data android:mimeType=" vnd.android.cursor.item/phone_v2" /> < data android:mimeType=" vnd.android.cursor.item/person" /> < /intent-filter> < /activity>

在收到Action:android.intent.action.CALL后会启动Activity:OutgoingCallBroadcaster。在启动Activity之前最先会调用:PhoneApp,由于它继承了Application就是程序的入口
< application android:name=" PhoneApp" android:icon=" @drawable/ic_launcher_phone" android:label=" @string/phoneAppLabel" android:persistent=" true" > < /application>

关于Application类的作用主要是一些全局的初始化工作,静态对象给其他类使用;在onCreate()函数里会创建Phone phone对象,这是framework层的一个类com.android.internal.telephony.Phone,所以导入Eclipse后会报非常多错误,我是在Eclipse改动后在ubuntu14.04下进行编译生成apk的。在onCreate()下有这样一段代码进行初始化

if (phone == null) { // 初始化phone frameworks层 PhoneFactory.makeDefaultPhones(this); // 获取默认的phone对象 phone = PhoneFactory.getDefaultPhone(); mCM = CallManager.getInstance(); mCM.registerPhone(phone); // 创建一个的单例的 NotificationMgr对象。用来显示状态栏图标和控制其它状态栏 notificationMgr = NotificationMgr.init(this); //是一个phone的应用层服务,ITelephony.Stub的实现 phoneMgr = PhoneInterfaceManager.init(this, phone); // 开启Sip卡的服务 mHandler.sendEmptyMessage(EVENT_START_SIP_SERVICE); // 获取电话的类型PHONE_TYPE_CDMA、PHONE_TYPE_GSM、PHONE_TYPE_SIP int phoneType = phone.getPhoneType(); if (phoneType == Phone.PHONE_TYPE_CDMA) { // Create an instance of CdmaPhoneCallState and initialize it to // IDLE cdmaPhoneCallState = new CdmaPhoneCallState(); cdmaPhoneCallState.CdmaPhoneCallStateInit(); }if (BluetoothAdapter.getDefaultAdapter() != null) { // Start BluetoothHandsree even if device is not voice capable. // The device can still support VOIP. // 初始化蓝牙免提对象 mBtHandsfree = BluetoothHandsfree.init(this, mCM); // 开启一个蓝牙耳机服务 startService(new Intent(this, BluetoothHeadsetService.class)); } else { // Device is not bluetooth capable mBtHandsfree = null; } // 获取铃声对象 ringer = Ringer.init(this); // before registering for phone state changes PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG); // lock used to keep the processor awake, when we don' t care for the // display. mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, LOG_TAG); // Wake lock used to control proximity sensor behavior. if ((pm.getSupportedWakeLockFlags() & PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) != 0x0) { mProximityWakeLock = pm.newWakeLock( PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, LOG_TAG); } if (DBG) Log.d(LOG_TAG, " onCreate: mProximityWakeLock: " + mProximityWakeLock); // create mAccelerometerListener only if we are using the proximity // sensor if (proximitySensorModeEnabled()) { mAccelerometerListener = new AccelerometerListener(this, this); }mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); // get a handle to the service so that we can use it later when we // want to set the poke lock. mPowerManagerService = IPowerManager.Stub .asInterface(ServiceManager.getService(" power" )); // Create the CallController singleton, which is the interface // to the telephony layer for user-initiated telephony functionality // (like making outgoing calls.) callController = CallController.init(this); // ...and also the InCallUiState instance, used by the // CallController to // keep track of some " persistent state" of the in-call UI. inCallUiState = InCallUiState.init(this); // Create the CallNotifer singleton, which handles // asynchronous events from the telephony layer (like // launching the incoming-call UI when an incoming call comes // in.) notifier = CallNotifier.init(this, phone, ringer, mBtHandsfree, new CallLogAsync()); // 注冊ICC的状态 IccCard sim = phone.getIccCard(); if (sim != null) { if (VDBG) Log.v(LOG_TAG, " register for ICC status" ); sim.registerForNetworkLocked(mHandler, EVENT_SIM_NETWORK_LOCKED, null); }// register for MMI/USSD mCM.registerForMmiComplete(mHandler, MMI_COMPLETE, null); // 通过PhoneUtils跟踪CallManager PhoneUtils.initializeConnectionHandler(mCM); // Read platform settings for TTY feature mTtyEnabled = getResources().getBoolean(R.bool.tty_enabled); // 注冊广播的Action IntentFilter intentFilter = new IntentFilter( Intent.ACTION_AIRPLANE_MODE_CHANGED); intentFilter .addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); intentFilter .addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); intentFilter.addAction(Intent.ACTION_HEADSET_PLUG); intentFilter.addAction(Intent.ACTION_DOCK_EVENT); intentFilter.addAction(Intent.ACTION_BATTERY_LOW); intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); intentFilter .addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED); intentFilter .addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED); intentFilter .addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); if (mTtyEnabled) { intentFilter .addAction(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION); } intentFilter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); registerReceiver(mReceiver, intentFilter); // Use a separate receiver for ACTION_MEDIA_BUTTON broadcasts, // since we need to manually adjust its priority (to make sure // we get these intents *before* the media player.) IntentFilter mediaButtonIntentFilter = new IntentFilter( Intent.ACTION_MEDIA_BUTTON); // // Make sure we' re higher priority than the media player' s // MediaButtonIntentReceiver (which currently has the default // priority of zero; see apps/Music/AndroidManifest.xml.) mediaButtonIntentFilter.setPriority(1); // registerReceiver(mMediaButtonReceiver, mediaButtonIntentFilter); // set the default values for the preferences in the phone. PreferenceManager.setDefaultValues(this, R.xml.network_setting, false); PreferenceManager.setDefaultValues(this, R.xml.call_feature_setting, false); // Make sure the audio mode (along with some // audio-mode-related state of our own) is initialized // correctly, given the current state of the phone. PhoneUtils.setAudioMode(mCM); }


在这个过程中获取了phone、CallController、InCallUiState、CallNotifier、NotificationMgr、Ringer、BluetoothHandsfree、PhoneInterfaceManager、CallManager等对象和动态注冊广播消息。

接下来是启动Activity:OutgoingCallBroadcaster依据生命周期最先会运行onCreate函数。获取一个Intent:Intent intent = getIntent(); 得到下面信息Action和拨出号码:
  String action = intent.getAction();
  String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
  并推断该号码是不是紧急号码。假设是设置--> callNow = true; 启动InCallScreen--> mApp.displayCallScreen(); 无论callNow是true或false都会发送下面广播:

sendOrderedBroadcast(broadcastIntent, PERMISSION, new OutgoingCallReceiver(), null,// scheduler Activity.RESULT_OK,// initialCode number,// initialData: initial value for the result data null); // initialExtras

进入一个内部类:OutgoingCallReceiver处理完后--> finish()
public void onReceive(Context context, Intent intent) { doReceive(context, intent); finish(); }

在广播里推断是否已经启动InCallScreen--> alreadyCalled = intent.getBooleanExtra(OutgoingCallBroadcaster.EXTRA_ALREADY_CALLED, false); 假设alreadyCalled为false就做一些初始化工作。设置Intent为ACTION_CALL。并带上号码和uri。启动InCallScreen--> startSipCallOptionHandler(context, intent, uri, number);
private void startSipCallOptionHandler(Context context, Intent intent, Uri uri, String number) { if (VDBG) { Log.i(TAG, " startSipCallOptionHandler..." ); Log.i(TAG, " - intent: " + intent); Log.i(TAG, " - uri: " + uri); Log.i(TAG, " - number: " + number); }// Create a copy of the original CALL intent that started the whole // outgoing-call sequence.This intent will ultimately be passed to // CallController.placeCall() after the SipCallOptionHandler step.Intent newIntent = new Intent(Intent.ACTION_CALL, uri); newIntent.putExtra(EXTRA_ACTUAL_NUMBER_TO_DIAL, number); PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent); // Finally, launch the SipCallOptionHandler, with the copy of the // original CALL intent stashed away in the EXTRA_NEW_CALL_INTENT // extra.Intent selectPhoneIntent = new Intent(ACTION_SIP_SELECT_PHONE, uri); selectPhoneIntent.setClass(context, SipCallOptionHandler.class); selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent); selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (DBG) Log.v(TAG, " startSipCallOptionHandler(): " + " calling startActivity: " + selectPhoneIntent); context.startActivity(selectPhoneIntent); // ...and see SipCallOptionHandler.onCreate() for the next step of the sequence. }

启动了SipCallOptionHandler类在onCreate()的最后会调用--> setResultAndFinish();
private void setResultAndFinish() { runOnUiThread(new Runnable() { public void run() { if (mOutgoingSipProfile != null) { if (!isNetworkConnected()) { showDialog(DIALOG_NO_INTERNET_ERROR); return; } if (DBG) Log.v(TAG, " primary SIP URI is " + mOutgoingSipProfile.getUriString()); createSipPhoneIfNeeded(mOutgoingSipProfile); mIntent.putExtra(OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI, mOutgoingSipProfile.getUriString()); if (mMakePrimary) { mSipSharedPreferences.setPrimaryAccount( mOutgoingSipProfile.getUriString()); } }if (mUseSipPhone & & mOutgoingSipProfile == null) { showDialog(DIALOG_START_SIP_SETTINGS); return; } else { // Woo hoo -- it' s finally OK to initiate the outgoing call! PhoneApp.getInstance().callController.placeCall(mIntent); } finish(); } }); }

正常情况会跑到--> PhoneApp.getInstance().callController.placeCall(mIntent); 之后Activity:SipCallOptionHandler会finish。 在CallController.java类中在placeCall这个函数有一段凝视说明调用流程
/** * Initiate an outgoing call. * * 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 initate 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. */ public void placeCall(Intent intent) { log(" placeCall()...intent = " + intent); if (VDBG) log(" extras = " + intent.getExtras()); final InCallUiState inCallUiState = mApp.inCallUiState; // TODO: Do we need to hold a wake lock while this method runs? //Or did we already acquire one somewhere earlier //in this sequence (like when we first received the CALL intent?)if (intent == null) { Log.wtf(TAG, " placeCall: called with null intent" ); throw new IllegalArgumentException(" placeCall: called with null intent" ); }String action = intent.getAction(); Uri uri = intent.getData(); if (uri == null) { Log.wtf(TAG, " placeCall: intent had no data" ); throw new IllegalArgumentException(" placeCall: intent had no data" ); }String scheme = uri.getScheme(); String number = PhoneNumberUtils.getNumberFromIntent(intent, mApp); if (VDBG) { log(" - action: " + action); log(" - uri: " + uri); log(" - scheme: " + scheme); log(" - number: " + number); }// This method should only be used with the various flavors of CALL // intents.(It doesn' t make sense for any other action to trigger an // outgoing call!) if (!(Intent.ACTION_CALL.equals(action) || Intent.ACTION_CALL_EMERGENCY.equals(action) || Intent.ACTION_CALL_PRIVILEGED.equals(action))) { Log.wtf(TAG, " placeCall: unexpected intent action " + action); throw new IllegalArgumentException(" Unexpected action: " + action); }// Check to see if this is an OTASP call (the " activation" call // used to provision CDMA devices), and if so, do some // OTASP-specific setup. Phone phone = mApp.mCM.getDefaultPhone(); if (TelephonyCapabilities.supportsOtasp(phone)) { checkForOtaspCall(intent); }// Clear out the " restore mute state" flag since we' re // initiating a brand-new call. // // (This call to setRestoreMuteOnInCallResume(false) informs the // phone app that we' re dealing with a new connection // (i.e. placing an outgoing call, and NOT handling an aborted // " Add Call" request), so we should let the mute state be handled // by the PhoneUtils phone state change handler.) mApp.setRestoreMuteOnInCallResume(false); // If a provider is used, extract the info to build the // overlay and route the call.The overlay will be // displayed when the InCallScreen becomes visible. if (PhoneUtils.hasPhoneProviderExtras(intent)) { inCallUiState.setProviderOverlayInfo(intent); } else { inCallUiState.clearProviderOverlayInfo(); }CallStatusCode status = placeCallInternal(intent); if (status == CallStatusCode.SUCCESS) { if (DBG) log(" ==> placeCall(): success from placeCallInternal(): " + status); // There' s no " error condition" that needs to be displayed to // the user, so clear out the InCallUiState' s " pending call // status code" . inCallUiState.clearPendingCallStatusCode(); // Notify the phone app that a call is beginning so it can // enable the proximity sensor mApp.setBeginningCall(true); } else { log(" ==> placeCall(): failure code from placeCallInternal(): " + status); // Handle the various error conditions that can occur when // initiating an outgoing call, typically by directing the // InCallScreen to display a diagnostic message (via the // " pending call status code" flag.) handleOutgoingCallError(status); }// Finally, regardless of whether we successfully initiated the // outgoing call or not, force the InCallScreen to come to the // foreground. // // (For successful calls the the user will just see the normal // in-call UI.Or if there was an error, the InCallScreen will // notice the InCallUiState pending call status code flag and display an // error indication instead.)// TODO: double-check the behavior of mApp.displayCallScreen() // if the InCallScreen is already visible: // - make sure it forces the UI to refresh // - make sure it does NOT launch a new InCallScreen on top //of the current one (i.e. the Back button should not take //you back to the previous InCallScreen) // - it' s probably OK to go thru a fresh pause/resume sequence //though (since that should be fast now) // - if necessary, though, maybe PhoneApp.displayCallScreen() //could notice that the InCallScreen is already in the foreground, //and if so simply call updateInCallScreen() instead.mApp.displayCallScreen(); }

最后启动InCallScreen--> startActivity(createInCallIntent());
/** * Starts the InCallScreen Activity. */ /* package */void displayCallScreen() { if (VDBG) Log.d(LOG_TAG, " displayCallScreen()..." ); // On non-voice-capable devices we shouldn' t ever be trying to // bring up the InCallScreen in the first place. if (!sVoiceCapable) { Log.w(LOG_TAG, " displayCallScreen() not allowed: non-voice-capable device" , new Throwable(" stack dump" )); // Include a stack trace since // this warning // indicates a bug in our // caller return; }try { startActivity(createInCallIntent()); } catch (ActivityNotFoundException e) { // It' s possible that the in-call UI might not exist (like on // non-voice-capable devices), so don' t crash if someone // accidentally tries to bring it up... Log.w(LOG_TAG, " displayCallScreen: transition to InCallScreen failed: " + e); } Profiler.callScreenRequested(); }/* package */static Intent createInCallIntent() { Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NO_USER_ACTION); intent.setClassName(" com.android.phone" , getCallScreenClassName()); return intent; } //获取InCallScreen的包名 static String getCallScreenClassName() { return InCallScreen.class.getName(); }

到这里一次普通的拨号界面启动流程就完毕了。
有非常多的全局的初始化工作在PhoneApp.java中已经完毕
Android4.0(Phone)拨号启动过程分析

文章图片


【Android4.0(Phone)拨号启动过程分析】
















    推荐阅读