Android4.0(Phone)来电过程分析
在开机时。系统会启动PhoneApp类,那是由于在AndroidManifest.xml文件里配置了
<application
android:name="PhoneApp"
android:icon="@drawable/ic_launcher_phone"
android:label="@string/phoneAppLabel"
android:persistent="true" >
</application>
因为配置了android:persistent="true"属性,而且Phone.apk是安装在/system/app/文件夹下的。所以在开机时会自己主动启动PhoneApp类。在PhoneApp初始化时会进入回调函数:onCreate()
@Override
public void onCreate() {
//.......
if (phone == null) {
//........
// 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());
//........
}
}
对CallNotifier对象进行初始化,Callnotifier主要是对电话状态的监听
在CallNotifier的构造函数里
private CallNotifier(PhoneApp app, Phone phone, Ringer ringer,
BluetoothHandsfree btMgr, CallLogAsync callLog) {
//............
//跟CallManager注冊通知。跟Framework通訊
registerForNotifications();
//...............
}
在CallNotifier.java中向CallManager类(Framework层)注冊监听消息
private void registerForNotifications() {
mCM.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
mCM.registerForPreciseCallStateChanged(this, PHONE_STATE_CHANGED, null);
mCM.registerForDisconnect(this, PHONE_DISCONNECT, null);
mCM.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
mCM.registerForIncomingRing(this, PHONE_INCOMING_RING, null);
mCM.registerForCdmaOtaStatusChange(this, EVENT_OTA_PROVISION_CHANGE, null);
mCM.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
mCM.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
mCM.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
mCM.registerForInCallVoicePrivacyOn(this, PHONE_ENHANCED_VP_ON, null);
mCM.registerForInCallVoicePrivacyOff(this, PHONE_ENHANCED_VP_OFF, null);
mCM.registerForRingbackTone(this, PHONE_RINGBACK_TONE, null);
mCM.registerForResendIncallMute(this, PHONE_RESEND_MUTE, null);
}
通过mCM.registerForNewRingingConnection(this,
PHONE_NEW_RINGING_CONNECTION, null);监听来电,在CallManager.java中
/**
* Register for getting notifications for change in the Call State {@link Call.State}
* This is called PreciseCallState because the call state is more precise than the
* {@link Phone.State} which can be obtained using the {@link PhoneStateListener}
*
* Resulting events will have an AsyncResult in <code>Message.obj</code>.
* AsyncResult.userData will be set to the obj argument here.
* The <em>h</em> parameter is held only by a weak reference.
*/
public void registerForPreciseCallStateChanged(Handler h, int what, Object obj){
mPreciseCallStateRegistrants.addUnique(h, what, obj);
}
处理PHONE_NEW_RINGING_CONNECTION
<pre name="code" class="java"> @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case PHONE_NEW_RINGING_CONNECTION:
log("RINGING... (new)");
onNewRingingConnection((AsyncResult) msg.obj);
mSilentRingerRequested = false;
break;
//...........
}
}
什么时候会收到PHONE_NEW_RINGING_CONNECTION消息呢?当Modem(调制解调器)端收到来电信息时,会将相关来电信息通过AT指令发送给RILC。再通过RILC使用socket发送给RILJ,逐层向上传递,上传到Framework层,终于显示来电响铃界面。最后是通过下面广播上传到PhoneApp
在RIL中有一个内部类RILReceiver
class RILReceiver implements Runnable {
byte[] buffer;
RILReceiver() {
buffer = new byte[RIL_MAX_COMMAND_BYTES];
}
public void
run() {
int retryCount = 0;
try {for (;;) {
LocalSocket s = null;
LocalSocketAddress l;
try {
s = new LocalSocket();
l = new LocalSocketAddress(SOCKET_NAME_RIL,
LocalSocketAddress.Namespace.RESERVED);
s.connect(l);
} catch (IOException ex){
try {
if (s != null) {
s.close();
}
} catch (IOException ex2) {
//ignore failure to close after failure to connect
}
// don't print an error message after the the first time
// or after the 8th time
if (retryCount == 8) {
Log.e (LOG_TAG,
"Couldn't find '" + SOCKET_NAME_RIL
+ "' socket after " + retryCount
+ " times, continuing to retry silently");
} else if (retryCount > 0 && retryCount < 8) {
Log.i (LOG_TAG,
"Couldn't find '" + SOCKET_NAME_RIL
+ "' socket; retrying after timeout");
}
try {
Thread.sleep(SOCKET_OPEN_RETRY_MILLIS);
} catch (InterruptedException er) {
}
retryCount++;
continue;
}
retryCount = 0;
mSocket = s;
Log.i(LOG_TAG, "Connected to '" + SOCKET_NAME_RIL + "' socket");
int length = 0;
try {
InputStream is = mSocket.getInputStream();
for (;;) {
Parcel p;
length = readRilMessage(is, buffer);
if (length < 0) {
// End-of-stream reached
break;
}
p = Parcel.obtain();
p.unmarshall(buffer, 0, length);
p.setDataPosition(0);
//Log.v(LOG_TAG, "Read packet: " + length + " bytes");
processResponse(p);
p.recycle();
}
} catch (java.io.IOException ex) {
Log.i(LOG_TAG, "'" + SOCKET_NAME_RIL + "' socket closed",
ex);
} catch (Throwable tr) {
Log.e(LOG_TAG, "Uncaught exception read length=" + length +
"Exception:" + tr.toString());
}
Log.i(LOG_TAG, "Disconnected from '" + SOCKET_NAME_RIL
+ "' socket");
setRadioState (RadioState.RADIO_UNAVAILABLE);
try {
mSocket.close();
} catch (IOException ex) {
}
mSocket = null;
RILRequest.resetSerial();
// Clear request list on close
clearRequestsList(RADIO_NOT_AVAILABLE, false);
}} catch (Throwable tr) {
Log.e(LOG_TAG,"Uncaught exception", tr);
}
/* We're disconnected so we don't know the ril version */
notifyRegistrantsRilConnectionChanged(-1);
}
}
在RIL的构造函数中创建RILReceiver对象
public RIL(Context context, int preferredNetworkType, int cdmaSubscription) {
//..................
mReceiver = new RILReceiver();
mReceiverThread = new Thread(mReceiver, "RILReceiver");
mReceiverThread.start();
//.................
}
}
在前面的分析中知道RIL在PhoneApp中就进行初始化了,RILReceiver是一个线程使用Socket通信。在线程中调用processResponse(p)
private void processResponse (Parcel p) {
int type;
type = p.readInt();
if (type == RESPONSE_UNSOLICITED) {
//主动响应
processUnsolicited (p);
} else if (type == RESPONSE_SOLICITED) {
//响应请求
processSolicited (p);
}
releaseWakeLockIfDone();
}
来电调用的是下面函数
private void processUnsolicited (Parcel p) {
//..............
case RIL_UNSOL_CALL_RING:
ret = responseCallRing(p);
break;
//..............
case RIL_UNSOL_CALL_RING:
if (RILJ_LOGD)
unsljLogRet(response, ret);
if (mRingRegistrant != null) {
mRingRegistrant.notifyRegistrant(
new AsyncResult (null, ret, null));
}
break;
//..............
}
进入Registrant类中
public void notifyRegistrant(AsyncResult ar){
internalNotifyRegistrant (ar.result, ar.exception);
}
/*package*/ void
internalNotifyRegistrant (Object result, Throwable exception)
{
Handler h = getHandler(); if (h == null) {
clear();
} else {
Message msg = Message.obtain(); msg.what = what; msg.obj = new AsyncResult(userObj, result, exception); h.sendMessage(msg);
}
}
当PhoneApp收到:PHONE_NEW_RINGING_CONNECTION后
/**
* Handles a "new ringing connection" event from the telephony layer.
*/
private void onNewRingingConnection(AsyncResult r) {
Connection c = (Connection) r.result;
log("onNewRingingConnection(): state = " + mCM.getState() + ", conn = { " + c + " }");
Call ringing = c.getCall();
Phone phone = ringing.getPhone(); // Check for a few cases where we totally ignore incoming calls.
if (ignoreAllIncomingCalls(phone)) {
// Immediately reject the call, without even indicating to the user
// that an incoming call occurred. (This will generally send the
// caller straight to voicemail, just as if we *had* shown the
// incoming-call UI and the user had declined the call.)
PhoneUtils.hangupRingingCall(ringing);
return;
} if (c == null) {
Log.w(LOG_TAG, "CallNotifier.onNewRingingConnection(): null connection!");
// Should never happen, but if it does just bail out and do nothing.
return;
} if (!c.isRinging()) {
Log.i(LOG_TAG, "CallNotifier.onNewRingingConnection(): connection not ringing!");
// This is a very strange case: an incoming call that stopped
// ringing almost instantly after the onNewRingingConnection()
// event. There's nothing we can do here, so just bail out
// without doing anything. (But presumably we'll log it in
// the call log when the disconnect event comes in...)
return;
} // Stop any signalInfo tone being played on receiving a Call
stopSignalInfoTone(); Call.State state = c.getState();
// State will be either INCOMING or WAITING.
if (VDBG) log("- connection is ringing! state = " + state);
// if (DBG) PhoneUtils.dumpCallState(mPhone); // No need to do any service state checks here (like for
// "emergency mode"), since in those states the SIM won't let
// us get incoming connections in the first place. // TODO: Consider sending out a serialized broadcast Intent here
// (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the
// ringer and going to the in-call UI. The intent should contain
// the caller-id info for the current connection, and say whether
// it would be a "call waiting" call or a regular ringing call.
// If anybody consumed the broadcast, we'd bail out without
// ringing or bringing up the in-call UI.
//
// This would give 3rd party apps a chance to listen for (and
// intercept) new ringing connections. An app could reject the
// incoming call by consuming the broadcast and doing nothing, or
// it could "pick up" the call (without any action by the user!)
// via some future TelephonyManager API.
//
// See bug 1312336 for more details.
// We'd need to protect this with a new "intercept incoming calls"
// system permission. // Obtain a partial wake lock to make sure the CPU doesn't go to
// sleep before we finish bringing up the InCallScreen.
// (This will be upgraded soon to a full wake lock; see
// showIncomingCall().)
if (VDBG) log("Holding wake lock on new incoming connection.");
mApplication.requestWakeState(PhoneApp.WakeState.PARTIAL); // - don't ring for call waiting connections
// - do this before showing the incoming call panel
if (PhoneUtils.isRealIncomingCall(state)) {
startIncomingCallQuery(c);
} else {
if (VDBG) log("- starting call waiting tone...");
if (mCallWaitingTonePlayer == null) {
mCallWaitingTonePlayer = new InCallTonePlayer(InCallTonePlayer.TONE_CALL_WAITING);
mCallWaitingTonePlayer.start();
}
// in this case, just fall through like before, and call
// showIncomingCall().
if (DBG) log("- showing incoming call (this is a WAITING call)...");
showIncomingCall();
} // Note we *don't* post a status bar notification here, since
// we're not necessarily ready to actually show the incoming call
// to the user. (For calls in the INCOMING state, at least, we
// still need to run a caller-id query, and we may not even ring
// at all if the "send directly to voicemail" flag is set.)
//
// Instead, we update the notification (and potentially launch the
// InCallScreen) from the showIncomingCall() method, which runs
// when the caller-id query completes or times out. if (VDBG) log("- onNewRingingConnection() done.");
}
调用showIncomingCall();函数显示来电界面
private void showIncomingCall() {
log("showIncomingCall()... phone state = " + mCM.getState());
// Before bringing up the "incoming call" UI, force any system
// dialogs (like "recent tasks" or the power dialog) to close first.
try {
ActivityManagerNative.getDefault().closeSystemDialogs("call");
} catch (RemoteException e) {
}
mApplication.preventScreenOn(true);
mApplication.requestWakeState(PhoneApp.WakeState.FULL);
// Post the "incoming call" notification *and* include the
// fullScreenIntent that'll launch the incoming-call UI.
// (This will usually take us straight to the incoming call
// screen, but if an immersive activity is running it'll just
// appear as a notification.)
if (DBG) log("- updating notification from showIncomingCall()...");
mApplication.notificationMgr.updateNotificationAndLaunchIncomingCallUi();
}
NotificationMgr.java
public void updateNotificationAndLaunchIncomingCallUi() {
// Set allowFullScreenIntent=true to indicate that we *should*
// launch the incoming call UI if necessary.
updateInCallNotification(true);
}
private void updateInCallNotification(boolean allowFullScreenIntent) {
// incoming call is ringing:
if (hasRingingCall) {
if (DBG) log("- Using hi-pri notification for ringing call!");
// This is a high-priority event that should be shown even if the
// status bar is hidden or if an immersive activity is running.
notification.flags |= Notification.FLAG_HIGH_PRIORITY;
notification.tickerText = expandedViewLine2;
if (allowFullScreenIntent) {
if (DBG) log("- Setting fullScreenIntent: " + inCallPendingIntent);
notification.fullScreenIntent = inCallPendingIntent;
Call ringingCall = mCM.getFirstActiveRingingCall();
if ((ringingCall.getState() == Call.State.WAITING) && !mApp.isShowingCallScreen()) {
Log.i(LOG_TAG, "updateInCallNotification: call-waiting! force relaunch...");
// Cancel the IN_CALL_NOTIFICATION immediately before
// (re)posting it; this seems to force the
// NotificationManager to launch the fullScreenIntent.
mNotificationManager.cancel(IN_CALL_NOTIFICATION);
}
}
}
if (DBG) log("Notifying IN_CALL_NOTIFICATION: " + notification);
mNotificationManager.notify(IN_CALL_NOTIFICATION,
notification);
// Finally, refresh the mute and speakerphone notifications (since
// some phone state changes can indirectly affect the mute and/or
// speaker state).
updateSpeakerNotification();
updateMuteNotification();
}
PendingIntent inCallPendingIntent =
PendingIntent.getActivity(mContext, 0,
PhoneApp.createInCallIntent(), 0);
notification.contentIntent = inCallPendingIntent;
/* 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;
}
static String getCallScreenClassName() {
return InCallScreen.class.getName();
}
通过PendingIntent来启动InCallScreen来电界面,接听后就跟拨号界面一样了。
在測试android:persistent="true"时。编写了一个測试程序,一定要安装在system/app/文件夹下。在开机时才会启动。在程序启动后不会被系统回收,很easy
<? xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dzt.persistentdemo"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" /> <application
android:name="PersionApp"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:persistent="true"
android:theme="@style/AppTheme" >
<activity
android:name="com.dzt.persistentdemo.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application> </manifest>
开机后会打印在程序中加入的消息
sh-4.2# logcat -v time | grep PersionApp
01-02 00:18:46.090 I/PersionApp( 1033): [PersionApp]------------------->onCreate test
01-02 00:18:46.090 I/PersionApp( 1033): [Persion]----------------------------->Persion
01-02 00:18:46.090 I/PersionApp( 1033): [PersionApp]------------------->onCreate persion = null
Android4.0(Phone)来电过程分析的更多相关文章
- 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 ...
- QT210 android2.3 和android4.0 烧写编译日记
QT210下载烧录编译android2.3过程 工作环境:ubuntu12.04.5 | QT210开发板光盘 | QT210开发板 android2.3编译环境:gcc version 4.4.7 ...
- Android6.0之来电转接号码显示修改
Android6.0之来电转接号码显示修改 手机来电转接界面是在,点开Dialer-搜索框右边的三个点-设置-通话账户(要先插卡)-中国移动或者中国联通--来电转接--第一行,显示始终转接 将所有来电 ...
- 【原】webapp开发中兼容Android4.0以下版本的css hack
话说现在的手机型号越来越多,主要还是android和ios这2个巨头称霸了江湖,而他们自带的浏览器内核是webkit,那对于做移动网页开发的同事来说,一般只要做好webkit内核浏览器的展现效果就行了 ...
- 一个Activity掌握Android4.0新控件 (转)
原文地址:http://blog.csdn.net/lavor_zl/article/details/51261380 谷歌在推出Android4.0的同时推出了一些新控件,Android4.0中最常 ...
- 深入浅出-Android系统移植与平台开发(一)- Android4.0系统的下载与编译
作者:唐老师,华清远见嵌入式学院讲师. 一.Android4.0系统的下载与编译 Android系统的下载与编译,Google的官方网站上已经给出了详细的说明,请参照Android的官方网址: htt ...
- 深入浅出 - 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下 ...
- [转]使用ant让Android自动打包的build.xml,自动生成签名的apk文件(支持android4.0以上的版本)
在android4.0以后的sdk里那个脚本就失效了,主要是因为 apkbuilder这个程序不见了: 人家sdk升级,我们的脚本也要跟上趟,修改一下喽. 上网一查,大家的文章还停留在我去年的脚本程度 ...
- android4.0浏览器在eclipse中编译的步骤
工程源码: 注意: 如果下载已经修过的源码,只要进行3.4.8步骤就应该可以了. eclipse版本:adt-bundle-windows (Android Developer Tools Build ...
随机推荐
- ECSHOP如何增加红包序列号字符
ECSHOP系统线下发放红包时系统生成的红包序列号是在10000的基础上增加四位随机数字.如果当我们要发放大额度红包的时候,这样的序列号规 则难免给人不安全的感觉,万一有无聊的人,蒙几个红包序列号出来 ...
- WPF学习之绘图和动画--DarrenF
Blend作为专门的设计工具让WPF如虎添翼,即能够帮助不了解编程的设计师快速上手,又能够帮助资深开发者快速建立图形或者动画的原型. 1.1 WPF绘图 与传统的.net开发使用GDI+进行绘图不 ...
- Three.js 3D打印数据模型文件(.STL)载入中
3DPrint是现在和未来10年度科技产品的主流之中.广泛的. 对于电子商务类3D打印网站.一个主要功能就是商品3D呈现的方式,那是,3D数据可视化技术. HTML5(WebGL)它可以用于构建3D查 ...
- SICP的一些个人看法
网上搜书的时候,看到非常多人将这本书神话. 坦率地说,个人认为这本书过于学术化, 没什么实际project价值.一大堆题目也基本是高中数学竞赛题类似,浪费时间. 软件的核心技术是什么? 1> ...
- C++习题 对象转换
[Submit][Status][Web Board] Description 定义一个Teacher(教师)类(教师号,姓名,性别,薪金)和一个Student(学生)类(学号,姓名,性别,成绩),二 ...
- iOS相机去黑框
自己定义相机的时候,调用系统的相机,因为相机的分辨率,会出现短小的矩形框,总会出现黑色边框,例如以下图: 假设想实现全屏相机的话,这样做就能够了: CALayer *viewLayer = self. ...
- 【游戏】2048及各种变种大集合汇总【更新ing~新版Floppy2048 - 恒星聚变版 - 恶搞改数据】
threes - 鼻祖 手机版:http://asherv.com/threes/ js版:http://threesjs.com/ 2048 - 原版 http://gabrielecirulli. ...
- Maven搭建SpringMVC+Hibernate项目详解(转)
前言 今天复习一下SpringMVC+Hibernate的搭建,本来想着将Spring-Security权限控制框架也映入其中的,但是发现内容太多了,Spring-Security的就留在下一篇吧,这 ...
- 嵌入式Linux学习小结
这两个月一直在学习Linux.作为一名刚開始学习的人,学习期间难免磕磕碰碰.走弯路,可是,抱着不怕失败.多尝试的信念,终于还是坚持下来了. 如今已经清楚Linux的框架,知道怎么去开发一个Linux程 ...
- XML文件编码问题
这两天的过程中的一个项目,以解决编码格式ANSI的xml当文件.我遇到了一些问题.下面的例子现在将总结分析过程. 通过win7记事本或notepad++创建一个xml文件test_source: &l ...