Android4.0 (Phone) Call Process Analysis
The system starts the PhoneApp class at startup, because the PhoneApp class is configured in the AndroidManifest. xml file.
Because android: persistent = "true" enabled is configured, and phone.apk is installed in the/system/app/directory, the PhoneApp class is automatically started when it is started. During PhoneApp initialization, the callback function onCreate () will be entered ()
@Overridepublic 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());//........}}
Initializes the CallNotifier object. Callnotifier monitors the phone status.
In the CallNotifier Constructor
Private CallNotifier (PhoneApp app, Phone phone, Ringer ringer, fig, CallLogAsync callLog ){//............ // register the notification with CallManager, and use registerforconfigurations () with Framework ();//...............}
Register the listening message to the CallManager class (Framework layer) in CallNotifier. java.
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); }
Listen for incoming calls through mCM. registerForNewRingingConnection (this, phone_new_ring_connection, null); In 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); }
Process 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; //........... } }
When will I receive the phone_new_ring_connection message? When the Modem receives the incoming call information, it sends the call information to RILC through the AT command, and then sends the call information to RILJ through RILC using the socket, which is passed up layer by layer, upload to the Framework layer to display the incoming call response interface. Finally, upload to PhoneApp through the following broadcast
There is an internal class rilpolicer in RIL
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); } }
Create an RILReceiver object in the RIL constructor. public RIL(Context context, int preferredNetworkType, int cdmaSubscription) { //.................. mReceiver = new RILReceiver(); mReceiverThread = new Thread(mReceiver, "RILReceiver"); mReceiverThread.start(); //................. } }
In the previous analysis, we know that RIL is initialized in PhoneApp, and RILReceiver is a thread that uses Socket for communication. Call processResponse (p) in the thread)
Private void processResponse (Parcel p) {int type; type = p. readInt (); if (type = RESPONSE_UNSOLICITED) {// actively responds to processUnsolicited (p);} else if (type = RESPONSE_SOLICITED) {// Response Request processSolicited (p);} releaseWakeLockIfDone ();}
Call the following functions: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;//..............}
Entering the Registrant class
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); } }
After PhoneApp receives: 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."); }
Call showIncomingCall (). The function displays the caller interface.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();}
Use PendingIntent to start the InCallScreen call interface. After receiving the call, it will be the same as the dialing interface.When testing android: persistent = "true", a test program is compiled and must be installed in the system/app/directory, it will not be recycled by the system after the program is started, which is very simple
After the device is started, messages added to the program are printed.
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
Sample Code: http://download.csdn.net/detail/deng0zhaotai/7714163