首页 > 代码库 > Android4.0(Phone)拨号启动过程分析(一)

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

     由于工作的需要,需要修改原生的Phone程序,现在就好好看下来电与拨号是如何处理的;不管是拨号还是来电,调用的都是Phone程序,由于很多类都涉及到framework层,比较复杂;先从简单的拨号分析,在外部拨号是由Action:android.intent.action.CALL_PRIVILEGEDandroid.intent.action.CALL发起,这里只分析android.intent.action.CALL的情况,程序目录结构:


可以在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中已经完成