当前位置: 代码迷 >> Android >> Android4.0(Phone)拨号起步过程分析(一)
  详细解决方案

Android4.0(Phone)拨号起步过程分析(一)

热度:20   发布时间:2016-04-28 04:33:11.0
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中已经完成


  相关解决方案