由于工作的需要,需要修改原生的Phone程序,现在就好好看下来电与拨号是如何处理的;不管是拨号还是来电,调用的都是Phone程序,由于很多类都涉及到framework层,比较复杂;先从简单的拨号分析,在外部拨号是由Action:android.intent.action.CALL_PRIVILEGED或android.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中已经完成