zoukankan      html  css  js  c++  java
  • Android4.0(Phone)拨号启动过程分析(一)

         因为工作的须要。须要改动原生的Phone程序,如今就好好看下来电与拨号是怎样处理的;无论是拨号还是来电,调用的都是Phone程序,因为非常多类都涉及到framework层,比較复杂;先从简单的拨号分析。在外部拨号是由Action:android.intent.action.CALL_PRIVILEGEDandroid.intent.action.CALL发起,这里仅仅分析android.intent.action.CALL的情况,程序文件夹结构:


    能够在Phone程序的AndroidManifest.xml文件里找到

            <activity
                android:name="OutgoingCallBroadcaster"
                android:configChanges="orientation|screenSize|keyboardHidden"
                android:permission="android.permission.CALL_PHONE"
                android:theme="@android:style/Theme.NoDisplay" >
    
                <!--
                     CALL action intent filters, for the various ways
                     of initiating an outgoing call.
                -->
                <intent-filter>
                    <action android:name="android.intent.action.CALL" />
    
                    <category android:name="android.intent.category.DEFAULT" />
    
                    <data android:scheme="tel" />
                </intent-filter>
                <intent-filter android:icon="@drawable/ic_launcher_sip_call" >
                    <action android:name="android.intent.action.CALL" />
    
                    <category android:name="android.intent.category.DEFAULT" />
    
                    <data android:scheme="sip" />
                </intent-filter>
                <intent-filter>
                    <action android:name="android.intent.action.CALL" />
    
                    <category android:name="android.intent.category.DEFAULT" />
    
                    <data android:scheme="voicemail" />
                </intent-filter>
                <intent-filter>
                    <action android:name="android.intent.action.CALL" />
    
                    <category android:name="android.intent.category.DEFAULT" />
    
                    <data android:mimeType="vnd.android.cursor.item/phone" />
                    <data android:mimeType="vnd.android.cursor.item/phone_v2" />
                    <data android:mimeType="vnd.android.cursor.item/person" />
                </intent-filter>
    	</activity>
    在收到Action:android.intent.action.CALL后会启动Activity:OutgoingCallBroadcaster。在启动Activity之前最先会调用:PhoneApp,由于它继承了Application就是程序的入口
    <application
            android:name="PhoneApp"
            android:icon="@drawable/ic_launcher_phone"
            android:label="@string/phoneAppLabel"
            android:persistent="true" >
    </application> 

    关于Application类的作用主要是一些全局的初始化工作,静态对象给其他类使用;在onCreate()函数里会创建Phone phone对象,这是framework层的一个类com.android.internal.telephony.Phone,所以导入Eclipse后会报非常多错误,我是在Eclipse改动后在ubuntu14.04下进行编译生成apk的。在onCreate()下有这样一段代码进行初始化

    if (phone == null) {
    			// 初始化phone frameworks层
    			PhoneFactory.makeDefaultPhones(this);
    
    			// 获取默认的phone对象
    			phone = PhoneFactory.getDefaultPhone();
    
    			mCM = CallManager.getInstance();
    			mCM.registerPhone(phone);
    
    			// 创建一个的单例的 NotificationMgr对象。用来显示状态栏图标和控制其它状态栏
    			notificationMgr = NotificationMgr.init(this);
    
    			//是一个phone的应用层服务,ITelephony.Stub的实现
    			phoneMgr = PhoneInterfaceManager.init(this, phone);
    			// 开启Sip卡的服务
    			mHandler.sendEmptyMessage(EVENT_START_SIP_SERVICE);
    			// 获取电话的类型PHONE_TYPE_CDMA、PHONE_TYPE_GSM、PHONE_TYPE_SIP
    			int phoneType = phone.getPhoneType();
    
    			if (phoneType == Phone.PHONE_TYPE_CDMA) {
    				// Create an instance of CdmaPhoneCallState and initialize it to
    				// IDLE
    				cdmaPhoneCallState = new CdmaPhoneCallState();
    				cdmaPhoneCallState.CdmaPhoneCallStateInit();
    			}
    
    			if (BluetoothAdapter.getDefaultAdapter() != null) {
    				// Start BluetoothHandsree even if device is not voice capable.
    				// The device can still support VOIP.
    				// 初始化蓝牙免提对象
    				mBtHandsfree = BluetoothHandsfree.init(this, mCM);
    				// 开启一个蓝牙耳机服务
    				startService(new Intent(this, BluetoothHeadsetService.class));
    			} else {
    				// Device is not bluetooth capable
    				mBtHandsfree = null;
    			}
    			// 获取铃声对象
    			ringer = Ringer.init(this);
    
    			// before registering for phone state changes
    			PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    			mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK
    					| PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG);
    			// lock used to keep the processor awake, when we don't care for the
    			// display.
    			mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
    					| PowerManager.ON_AFTER_RELEASE, LOG_TAG);
    			// Wake lock used to control proximity sensor behavior.
    			if ((pm.getSupportedWakeLockFlags() & PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) != 0x0) {
    				mProximityWakeLock = pm.newWakeLock(
    						PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, LOG_TAG);
    			}
    			if (DBG)
    				Log.d(LOG_TAG, "onCreate: mProximityWakeLock: "
    						+ mProximityWakeLock);
    
    			// create mAccelerometerListener only if we are using the proximity
    			// sensor
    			if (proximitySensorModeEnabled()) {
    				mAccelerometerListener = new AccelerometerListener(this, this);
    			}
    
    			mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    
    			// get a handle to the service so that we can use it later when we
    			// want to set the poke lock.
    			mPowerManagerService = IPowerManager.Stub
    					.asInterface(ServiceManager.getService("power"));
    
    			// Create the CallController singleton, which is the interface
    			// to the telephony layer for user-initiated telephony functionality
    			// (like making outgoing calls.)
    			callController = CallController.init(this);
    			// ...and also the InCallUiState instance, used by the
    			// CallController to
    			// keep track of some "persistent state" of the in-call UI.
    			inCallUiState = InCallUiState.init(this);
    
    			// 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());
    
    			// 注冊ICC的状态
    			IccCard sim = phone.getIccCard();
    			if (sim != null) {
    				if (VDBG)
    					Log.v(LOG_TAG, "register for ICC status");
    				sim.registerForNetworkLocked(mHandler,
    						EVENT_SIM_NETWORK_LOCKED, null);
    			}
    
    			// register for MMI/USSD
    			mCM.registerForMmiComplete(mHandler, MMI_COMPLETE, null);
    
    			// 通过PhoneUtils跟踪CallManager
    			PhoneUtils.initializeConnectionHandler(mCM);
    
    			// Read platform settings for TTY feature
    			mTtyEnabled = getResources().getBoolean(R.bool.tty_enabled);
    
    			// 注冊广播的Action
    			IntentFilter intentFilter = new IntentFilter(
    					Intent.ACTION_AIRPLANE_MODE_CHANGED);
    			intentFilter
    					.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
    			intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
    			intentFilter
    					.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
    			intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
    			intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
    			intentFilter.addAction(Intent.ACTION_BATTERY_LOW);
    			intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
    			intentFilter
    					.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
    			intentFilter
    					.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
    			intentFilter
    					.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
    			if (mTtyEnabled) {
    				intentFilter
    						.addAction(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION);
    			}
    			intentFilter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
    			registerReceiver(mReceiver, intentFilter);
    
    			// Use a separate receiver for ACTION_MEDIA_BUTTON broadcasts,
    			// since we need to manually adjust its priority (to make sure
    			// we get these intents *before* the media player.)
    			IntentFilter mediaButtonIntentFilter = new IntentFilter(
    					Intent.ACTION_MEDIA_BUTTON);
    			//
    			// Make sure we're higher priority than the media player's
    			// MediaButtonIntentReceiver (which currently has the default
    			// priority of zero; see apps/Music/AndroidManifest.xml.)
    			mediaButtonIntentFilter.setPriority(1);
    			//
    			registerReceiver(mMediaButtonReceiver, mediaButtonIntentFilter);
    
    			// set the default values for the preferences in the phone.
    			PreferenceManager.setDefaultValues(this, R.xml.network_setting,
    					false);
    
    			PreferenceManager.setDefaultValues(this,
    					R.xml.call_feature_setting, false);
    
    			// Make sure the audio mode (along with some
    			// audio-mode-related state of our own) is initialized
    			// correctly, given the current state of the phone.
    			PhoneUtils.setAudioMode(mCM);
    		}

    在这个过程中获取了phone、CallController、InCallUiState、CallNotifier、NotificationMgr、Ringer、BluetoothHandsfree、PhoneInterfaceManager、CallManager等对象和动态注冊广播消息。

    接下来是启动Activity:OutgoingCallBroadcaster依据生命周期最先会运行onCreate函数。获取一个Intent:Intent intent = getIntent();得到下面信息Action和拨出号码:
     String action = intent.getAction();
     String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
     并推断该号码是不是紧急号码。假设是设置-->callNow = true;启动InCallScreen-->mApp.displayCallScreen();无论callNow是true或false都会发送下面广播:

    sendOrderedBroadcast(broadcastIntent, PERMISSION, new OutgoingCallReceiver(),
                                 null,  // scheduler
                                 Activity.RESULT_OK,  // initialCode
                                 number,  // initialData: initial value for the result data
                                 null);  // initialExtras
    进入一个内部类:OutgoingCallReceiver处理完后-->finish()
     public void onReceive(Context context, Intent intent) {
                doReceive(context, intent);
                finish();
            }  
    在广播里推断是否已经启动InCallScreen-->alreadyCalled = intent.getBooleanExtra(OutgoingCallBroadcaster.EXTRA_ALREADY_CALLED, false);假设alreadyCalled为false就做一些初始化工作。设置Intent为ACTION_CALL。并带上号码和uri。启动InCallScreen-->startSipCallOptionHandler(context, intent, uri, number);
    private void startSipCallOptionHandler(Context context, Intent intent,
                Uri uri, String number) {
            if (VDBG) {
                Log.i(TAG, "startSipCallOptionHandler...");
                Log.i(TAG, "- intent: " + intent);
                Log.i(TAG, "- uri: " + uri);
                Log.i(TAG, "- number: " + number);
            }
    
            // Create a copy of the original CALL intent that started the whole
            // outgoing-call sequence.  This intent will ultimately be passed to
            // CallController.placeCall() after the SipCallOptionHandler step.
    
            Intent newIntent = new Intent(Intent.ACTION_CALL, uri);
            newIntent.putExtra(EXTRA_ACTUAL_NUMBER_TO_DIAL, number);
            PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent);
    
            // Finally, launch the SipCallOptionHandler, with the copy of the
            // original CALL intent stashed away in the EXTRA_NEW_CALL_INTENT
            // extra.
    
            Intent selectPhoneIntent = new Intent(ACTION_SIP_SELECT_PHONE, uri);
            selectPhoneIntent.setClass(context, SipCallOptionHandler.class);
            selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent);
            selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            if (DBG) Log.v(TAG, "startSipCallOptionHandler(): " +
                    "calling startActivity: " + selectPhoneIntent);
            context.startActivity(selectPhoneIntent);
            // ...and see SipCallOptionHandler.onCreate() for the next step of the sequence.
        }
    启动了SipCallOptionHandler类在onCreate()的最后会调用-->setResultAndFinish();
    private void setResultAndFinish() {
            runOnUiThread(new Runnable() {
                public void run() {
                    if (mOutgoingSipProfile != null) {
                        if (!isNetworkConnected()) {
                            showDialog(DIALOG_NO_INTERNET_ERROR);
                            return;
                        }
                        if (DBG) Log.v(TAG, "primary SIP URI is " +
                                mOutgoingSipProfile.getUriString());
                        createSipPhoneIfNeeded(mOutgoingSipProfile);
                        mIntent.putExtra(OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI,
                                mOutgoingSipProfile.getUriString());
                        if (mMakePrimary) {
                            mSipSharedPreferences.setPrimaryAccount(
                                    mOutgoingSipProfile.getUriString());
                        }
                    }
    
                    if (mUseSipPhone && mOutgoingSipProfile == null) {
                        showDialog(DIALOG_START_SIP_SETTINGS);
                        return;
                    } else {
                        // Woo hoo -- it's finally OK to initiate the outgoing call!
                        PhoneApp.getInstance().callController.placeCall(mIntent);
                    }
                    finish();
                }
            });
        }
    正常情况会跑到-->PhoneApp.getInstance().callController.placeCall(mIntent);之后Activity:SipCallOptionHandler会finish。 在CallController.java类中在placeCall这个函数有一段凝视说明调用流程
    /**
         * Initiate an outgoing call.
         *
         * Here's the most typical outgoing call sequence:
         *
         *  (1) OutgoingCallBroadcaster receives a CALL intent and sends the
         *      NEW_OUTGOING_CALL broadcast
         *
         *  (2) The broadcast finally reaches OutgoingCallReceiver, which stashes
         *      away a copy of the original CALL intent and launches
         *      SipCallOptionHandler
         *
         *  (3) SipCallOptionHandler decides whether this is a PSTN or SIP call (and
         *      in some cases brings up a dialog to let the user choose), and
         *      ultimately calls CallController.placeCall() (from the
         *      setResultAndFinish() method) with the stashed-away intent from step
         *      (2) as the "intent" parameter.
         *
         *  (4) Here in CallController.placeCall() we read the phone number or SIP
         *      address out of the intent and actually initate the call, and
         *      simultaneously launch the InCallScreen to display the in-call UI.
         *
         *  (5) We handle various errors by directing the InCallScreen to
         *      display error messages or dialogs (via the InCallUiState
         *      "pending call status code" flag), and in some cases we also
         *      sometimes continue working in the background to resolve the
         *      problem (like in the case of an emergency call while in
         *      airplane mode).  Any time that some onscreen indication to the
         *      user needs to change, we update the "status dialog" info in
         *      the inCallUiState and (re)launch the InCallScreen to make sure
         *      it's visible.
         */
         
     public void placeCall(Intent intent) {
            log("placeCall()...  intent = " + intent);
            if (VDBG) log("                extras = " + intent.getExtras());
    
            final InCallUiState inCallUiState = mApp.inCallUiState;
    
            // TODO: Do we need to hold a wake lock while this method runs?
            //       Or did we already acquire one somewhere earlier
            //       in this sequence (like when we first received the CALL intent?)
    
            if (intent == null) {
                Log.wtf(TAG, "placeCall: called with null intent");
                throw new IllegalArgumentException("placeCall: called with null intent");
            }
    
            String action = intent.getAction();
            Uri uri = intent.getData();
            if (uri == null) {
                Log.wtf(TAG, "placeCall: intent had no data");
                throw new IllegalArgumentException("placeCall: intent had no data");
            }
    
            String scheme = uri.getScheme();
            String number = PhoneNumberUtils.getNumberFromIntent(intent, mApp);
            if (VDBG) {
                log("- action: " + action);
                log("- uri: " + uri);
                log("- scheme: " + scheme);
                log("- number: " + number);
            }
    
            // This method should only be used with the various flavors of CALL
            // intents.  (It doesn't make sense for any other action to trigger an
            // outgoing call!)
            if (!(Intent.ACTION_CALL.equals(action)
                  || Intent.ACTION_CALL_EMERGENCY.equals(action)
                  || Intent.ACTION_CALL_PRIVILEGED.equals(action))) {
                Log.wtf(TAG, "placeCall: unexpected intent action " + action);
                throw new IllegalArgumentException("Unexpected action: " + action);
            }
    
            // Check to see if this is an OTASP call (the "activation" call
            // used to provision CDMA devices), and if so, do some
            // OTASP-specific setup.
            Phone phone = mApp.mCM.getDefaultPhone();
            if (TelephonyCapabilities.supportsOtasp(phone)) {
                checkForOtaspCall(intent);
            }
    
            // Clear out the "restore mute state" flag since we're
            // initiating a brand-new call.
            //
            // (This call to setRestoreMuteOnInCallResume(false) informs the
            // phone app that we're dealing with a new connection
            // (i.e. placing an outgoing call, and NOT handling an aborted
            // "Add Call" request), so we should let the mute state be handled
            // by the PhoneUtils phone state change handler.)
            mApp.setRestoreMuteOnInCallResume(false);
    
            // If a provider is used, extract the info to build the
            // overlay and route the call.  The overlay will be
            // displayed when the InCallScreen becomes visible.
            if (PhoneUtils.hasPhoneProviderExtras(intent)) {
                inCallUiState.setProviderOverlayInfo(intent);
            } else {
                inCallUiState.clearProviderOverlayInfo();
            }
    
            CallStatusCode status = placeCallInternal(intent);
    
            if (status == CallStatusCode.SUCCESS) {
                if (DBG) log("==> placeCall(): success from placeCallInternal(): " + status);
                // There's no "error condition" that needs to be displayed to
                // the user, so clear out the InCallUiState's "pending call
                // status code".
                inCallUiState.clearPendingCallStatusCode();
    
                // Notify the phone app that a call is beginning so it can
                // enable the proximity sensor
                mApp.setBeginningCall(true);
            } else {
                log("==> placeCall(): failure code from placeCallInternal(): " + status);
                // Handle the various error conditions that can occur when
                // initiating an outgoing call, typically by directing the
                // InCallScreen to display a diagnostic message (via the
                // "pending call status code" flag.)
                handleOutgoingCallError(status);
            }
    
            // Finally, regardless of whether we successfully initiated the
            // outgoing call or not, force the InCallScreen to come to the
            // foreground.
            //
            // (For successful calls the the user will just see the normal
            // in-call UI.  Or if there was an error, the InCallScreen will
            // notice the InCallUiState pending call status code flag and display an
            // error indication instead.)
    
            // TODO: double-check the behavior of mApp.displayCallScreen()
            // if the InCallScreen is already visible:
            // - make sure it forces the UI to refresh
            // - make sure it does NOT launch a new InCallScreen on top
            //   of the current one (i.e. the Back button should not take
            //   you back to the previous InCallScreen)
            // - it's probably OK to go thru a fresh pause/resume sequence
            //   though (since that should be fast now)
            // - if necessary, though, maybe PhoneApp.displayCallScreen()
            //   could notice that the InCallScreen is already in the foreground,
            //   and if so simply call updateInCallScreen() instead.
    
            mApp.displayCallScreen();
        }   
    最后启动InCallScreen-->startActivity(createInCallIntent());
    /**
    	 * Starts the InCallScreen Activity.
    	 */
    	/* package */void displayCallScreen() {
    		if (VDBG)
    			Log.d(LOG_TAG, "displayCallScreen()...");
    
    		// On non-voice-capable devices we shouldn't ever be trying to
    		// bring up the InCallScreen in the first place.
    		if (!sVoiceCapable) {
    			Log.w(LOG_TAG,
    					"displayCallScreen() not allowed: non-voice-capable device",
    					new Throwable("stack dump")); // Include a stack trace since
    													// this warning
    													// indicates a bug in our
    													// caller
    			return;
    		}
    
    		try {
    			startActivity(createInCallIntent());
    		} catch (ActivityNotFoundException e) {
    			// It's possible that the in-call UI might not exist (like on
    			// non-voice-capable devices), so don't crash if someone
    			// accidentally tries to bring it up...
    			Log.w(LOG_TAG,
    					"displayCallScreen: transition to InCallScreen failed: "
    							+ e);
    		}
    		Profiler.callScreenRequested();
    	}
    
    /* 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;
    	}
    	//获取InCallScreen的包名
    	static String getCallScreenClassName() {
    		return InCallScreen.class.getName();
    	}
    到这里一次普通的拨号界面启动流程就完毕了。

    有非常多的全局的初始化工作在PhoneApp.java中已经完毕


  • 相关阅读:
    友链
    OI日常
    P4451 [国家集训队]整数的lqp拆分 生成函数
    AT4831 [ABC155F] Perils in Parallel 生成树
    P4438 [HNOI/AHOI2018]道路 树DP
    CF383E Vowels 子集DP 容斥
    P5488 差分与前缀和 生成函数+多项式EXP
    CF115E Linear Kingdom Races 线段树优化DP
    CF49E Common ancestor 区间DP
    P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II 莫队二次离线
  • 原文地址:https://www.cnblogs.com/yfceshi/p/7241360.html
Copyright © 2011-2022 走看看