Android4.0(Phone)拨号启动过程分析(一)
因为工作的须要。须要改动原生的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中已经完毕
Android4.0(Phone)拨号启动过程分析(一)的更多相关文章
- [转]android4.0.3 修改启动动画和开机声音
本文转自:http://www.cnblogs.com/jqyp/archive/2012/03/07/2383973.html 1. Linux 系统启动,出现Linux小企鹅画面(reboot)( ...
- android4.0移植,拨号异常
D/dalvikvm( 2274): GC_CONCURRENT freed 206K, 12% free 6571K/7431K, paused 2ms+3ms D/dalvikvm( 2274): ...
- 001-快速搭建Spring web应用【springboot 2.0.4】-gradle、springboot的启动过程分析、gradle多模块构建
一.概述 学习<精通Spring MVC4>书籍笔记 二.笔记 1.快速构建Spring starter web项目几种方式 1>使用Spring Tool Suite生成Start ...
- android4.0 4.1 4.2 4.3 4.4新特性
http://blog.csdn.net/kaiyang45/article/details/7179349 4.0 http://digi.tech.qq.com/a/20120628/000827 ...
- ASP.Net Core MVC6 RC2 启动过程分析[偏源码分析]
入口程序 如果做过Web之外开发的人,应该记得这个是标准的Console或者Winform的入口.为什么会这样呢? .NET Web Development and Tools Blog ASP.NE ...
- 深入浅出 - Android系统移植与平台开发(三)- 编译并运行Android4.0模拟器
作者:唐老师,华清远见嵌入式学院讲师. 1. 编译Android模拟器 在Ubuntu下,我们可以在源码里编译出自己的模拟器及SDK等编译工具,当然这个和在windows里下载的看起来没有什么区别 ...
- QT210 Android4.0源码编译和烧录文档整理
开发环境说明: Ubuntu 12.04 LTS 32bit 源码文件目录: 勤研光盘2013-5-4\4.0 https://github.com/jackyh (建议在Linux环境下通过git下 ...
- 开机SystemServer到ActivityManagerService启动过程分析
开机SystemServer到ActivityManagerService启动过程 一 从Systemserver到AMS zygote-> systemserver:java入层口: /** ...
- Neutron分析(2)——neutron-server启动过程分析
neutron-server启动过程分析 1. /etc/init.d/neutron-server DAEMON=/usr/bin/neutron-server DAEMON_ARGS=" ...
随机推荐
- DOM中的节点属性
摘抄自:http://www.imooc.com/code/1589 nodeName 属性: 节点的名称,是只读的. 1. 元素节点的 nodeName 与标签名相同 2. 属性节点的 nodeNa ...
- P2846 [USACO08NOV]光开关Light Switching
题目描述 Farmer John tries to keep the cows sharp by letting them play with intellectual toys. One of th ...
- POJ 2728 Desert King(最优比例生成树 二分 | Dinkelbach迭代法)
Desert King Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 25310 Accepted: 7022 Desc ...
- 数学课(math)
数学课(math) 题目描述 wzy又来上数学课了-- 虽然他很菜,但是数学还是懂一丢丢的.老师出了一道题,给定一个包含nn个元素的集合P=1,2,3,-,nP=1,2,3,-,n,求有多少个集合A⊆ ...
- jspspy database help
.
- 设置pycharm的python版本
http://blog.csdn.net/github_35160620/article/details/52486986
- MYSQL常用命令——【转】
MYSQL常用命令 1.导出整个数据库mysqldump -u 用户名 -p --default-character-set=latin1 数据库名 > 导出的文件名(数据库默认编码是latin ...
- SQL索引基础
原文发布时间为:2011-02-19 -- 来源于本人的百度文章 [由搬家工具导入] 一、深入浅出理解索引结构 实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索 ...
- Jquery : 上下滚动--单行 批量多行 文字图片翻屏【转】
原文发布时间为:2010-02-01 -- 来源于本人的百度文章 [由搬家工具导入] 注:如果和左右滚动一起使用,则会出现冲突 一单行滚动(ad:http://www.gz138.com) <! ...
- Windows消息钩取
@author: dlive @date: 2016/12/19 0x01 SetWindowsHookEx() HHOOK SetWindowsHookEx( int idHook, //hook ...