zoukankan      html  css  js  c++  java
  • 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

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


  • 相关阅读:
    c++ 中bool 的默认值
    cocos2d CCLOG格式符号表
    c++数组指针bug
    cocos2d-x-2.2.6创建工程
    Nape实现坐标旋转角度回弹
    haxe 中使用音效
    haxe 嵌入swf 读取里面的内容
    haxe 配置
    Spring Tool Suite(STS)基本安装配置
    git提交忽略文件.gitignore内容
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5068021.html
Copyright © 2011-2022 走看看