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/目录下,在开机时才会启动,在程序启动后不会被系统回收,非常简单

<?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

示例代码:http://download.csdn.net/detail/deng0zhaotai/7714163


你可能感兴趣的:(android,phone,mt,RIL,Callnotifier)