博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android4.0(Phone)来电过程分析
阅读量:7229 次
发布时间:2019-06-29

本文共 18019 字,大约阅读时间需要 60 分钟。

在开机时。系统会启动PhoneApp类,那是由于在AndroidManifest.xml文件里配置了

因为配置了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 Message.obj.     * AsyncResult.userData will be set to the obj argument here.     * The h 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

 @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 PersionApp01-02 00:18:46.090 I/PersionApp( 1033): [PersionApp]------------------->onCreate test01-02 00:18:46.090 I/PersionApp( 1033): [Persion]----------------------------->Persion01-02 00:18:46.090 I/PersionApp( 1033): [PersionApp]------------------->onCreate persion = null

演示样例代码:

转载地址:http://ibdfm.baihongyu.com/

你可能感兴趣的文章
DTD和schema小总结
查看>>
去掉导航栏的黑线
查看>>
怎样让html加载完毕后加载js代码
查看>>
piwik 案例介绍
查看>>
敏感字过滤
查看>>
为什么我们要从 NodeJS 迁移到 Ruby on Rails
查看>>
Android 文件式数据库Realm
查看>>
Linux 面试知识点笔记
查看>>
论flex布局和box布局的华为meta8手机自带浏览器的兼容
查看>>
dubbo与springcloud初识
查看>>
iis web.config 配置示例
查看>>
归并排序
查看>>
java 的转义字符
查看>>
SharedPreferences的使用注意事项
查看>>
sofa-pbrpc高级用法
查看>>
Oracle 函数返回表实例2种写法实例
查看>>
mysql数据库主从复制
查看>>
Shell标准输出、标准错误 >/dev/null 2>&1
查看>>
Android自定义对话框(Dialog)位置,大小
查看>>
设置python的默认编码为utf8
查看>>