正文  软件开发 > 编程综合 >

Android4.0(Phone)拨号启动过程分析(二)

接上:Android4.0(Phone)拨号启动过程分析(一) InCallScreen处理来电和拨号的界面,接通电话也是这个界面,接下来分析InCallScreen类是如何处理拨号流程的...

接上:Android4.0(Phone)拨号启动过程分析(一)

InCallScreen处理来电和拨号的界面,接通电话也是这个界面,接下来分析InCallScreen类是如何处理拨号流程的;

@Override
	protected void onCreate(Bundle icicle) {
		Log.i(LOG_TAG, "onCreate()...  this = " + this);
		Profiler.callScreenOnCreate();
		super.onCreate(icicle);

		// Make sure this is a voice-capable device.
		if (!PhoneApp.sVoiceCapable) {
			// There should be no way to ever reach the InCallScreen on a
			// non-voice-capable device, since this activity is not exported by
			// our manifest, and we explicitly disable any other external APIs
			// like the CALL intent and ITelephony.showCallScreen().
			// So the fact that we got here indicates a phone app bug.
			Log.wtf(LOG_TAG, "onCreate() reached on non-voice-capable device");
			finish();
			return;
		}
		// 获取PhoneApp实例
		mApp = PhoneApp.getInstance();
		// 设置通话界面
		mApp.setInCallScreenInstance(this);

		// 添加这个标记可以让Activity显示在锁屏的上方
		int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
		if (mApp.getPhoneState() == Phone.State.OFFHOOK) {
			// While we are in call, the in-call screen should dismiss the
			// keyguard.
			// This allows the user to press Home to go directly home without
			// going through
			// an insecure lock screen.
			// But we do not want to do this if there is no active call so we do
			// not
			// bypass the keyguard if the call is not answered or declined.
			// 解除锁屏。只有锁屏界面不是加密的才能解锁。如果锁屏界面是加密的,那么用户解锁之后才能看到此窗口,除非设置了FLAG_SHOW_WHEN_LOCKED选项。
			flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
		}
		getWindow().addFlags(flags);

		// Also put the system bar (if present on this device) into
		// "lights out" mode any time we're the foreground activity.
		WindowManager.LayoutParams params = getWindow().getAttributes();
		params.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE;
		getWindow().setAttributes(params);

		setPhone(mApp.phone); // Sets mPhone

		mCM = mApp.mCM;
		log("- onCreate: phone state = " + mCM.getState());

		mBluetoothHandsfree = mApp.getBluetoothHandsfree();
		if (VDBG)
			log("- mBluetoothHandsfree: " + mBluetoothHandsfree);

		if (mBluetoothHandsfree != null) {
			// The PhoneApp only creates a BluetoothHandsfree instance in the
			// first place if BluetoothAdapter.getDefaultAdapter()
			// succeeds. So at this point we know the device is BT-capable.
			mAdapter = BluetoothAdapter.getDefaultAdapter();
			mAdapter.getProfileProxy(getApplicationContext(),
					mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);

		}

		requestWindowFeature(Window.FEATURE_NO_TITLE);

		// Inflate everything in incall_screen.xml and add it to the screen.
		setContentView(R.layout.incall_screen);
		// 初始化CallCard以及InCallTouchUi等截面
		initInCallScreen();
		// 注册关于Phone状态改变的监听事件,这也就是为什么Phone状态改变之后InCallScreen能够收到变化消息的原因,这一点我们在来电流程中也有提及;
		registerForPhoneStates();

		// No need to change wake state here; that happens in onResume() when we
		// are actually displayed.

		// Handle the Intent we were launched with, but only if this is the
		// the very first time we're being launched (ie. NOT if we're being
		// re-initialized after previously being shut down.)
		// Once we're up and running, any future Intents we need
		// to handle will come in via the onNewIntent() method.
		if (icicle == null) {
			if (DBG)
				log("onCreate(): this is our very first launch, checking intent...");
			// 该方法用于处理InCallScreen收到的Intent信息
			internalResolveIntent(getIntent());
		}

		Profiler.callScreenCreated();
		if (DBG)
			log("onCreate(): exit");
	}
只要分析三个函数:initInCallScreen、registerForPhoneStates、internalResolveIntent
private void initInCallScreen() {
		if (VDBG)
			log("initInCallScreen()...");

		// Have the WindowManager filter out touch events that are "too fat".
		getWindow().addFlags(
				WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);

		mInCallPanel = (ViewGroup) findViewById(R.id.inCallPanel);

		// Initialize the CallCard.
		mCallCard = (CallCard) findViewById(R.id.callCard);
		if (VDBG)
			log("  - mCallCard = " + mCallCard);
		mCallCard.setInCallScreenInstance(this);

		//初始化界面的UI布局
		initInCallTouchUi();

		// 助手类跟踪enabledness / UI控件的状态
		mInCallControlState = new InCallControlState(this, mCM);

		//助手类运行“Manage conference”的用户界面
		mManageConferenceUtils = new ManageConferenceUtils(this, mCM);

		// The DTMF Dialpad.
		// TODO: Don't inflate this until the first time it's needed.
		ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);
		stub.inflate();
		//DTMF拨号盘初始化  
		mDialerView = (DTMFTwelveKeyDialerView) findViewById(R.id.dtmf_twelve_key_dialer_view);
		if (DBG)
			log("- Found dialerView: " + mDialerView);

		// Sanity-check that (regardless of the device) at least the
		// dialer view is present:
		if (mDialerView == null) {
			Log.e(LOG_TAG, "onCreate: couldn't find dialerView",
					new IllegalStateException());
		}
		//创建DTMFTwelveKeyDialer实例
		mDialer = new DTMFTwelveKeyDialer(this, mDialerView);
	}
以下函数是通过CallManager类向Framework层注册一些状态,只要Framework层的状态改变就会通知上层应用修改UI;如果是来电就会在Handler收到PHONE_INCOMING_RING标记。实际上为观察者模式的运用
private void registerForPhoneStates() {
		if (!mRegisteredForPhoneStates) {
			mCM.registerForPreciseCallStateChanged(mHandler,
					PHONE_STATE_CHANGED, null);
			mCM.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
			mCM.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
			// register for the MMI complete message. Upon completion,
			// PhoneUtils will bring up a system dialog instead of the
			// message display class in PhoneUtils.displayMMIComplete().
			// We'll listen for that message too, so that we can finish
			// the activity at the same time.
			mCM.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
			mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
			mCM.registerForPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
			mCM.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED,
					null);
			mCM.registerForIncomingRing(mHandler, PHONE_INCOMING_RING, null);
			mCM.registerForNewRingingConnection(mHandler,
					PHONE_NEW_RINGING_CONNECTION, null);
			mRegisteredForPhoneStates = true;
		}
	}
internalResolveIntent是恢复Activity保存的状态
private void internalResolveIntent(Intent intent) {
		if (intent == null || intent.getAction() == null) {
			return;
		}
		String action = intent.getAction();
		if (DBG)
			log("internalResolveIntent: action=" + action);

		// In gingerbread and earlier releases, the InCallScreen used to
		// directly handle certain intent actions that could initiate phone
		// calls, namely ACTION_CALL and ACTION_CALL_EMERGENCY, and also
		// OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING.
		//
		// But it doesn't make sense to tie those actions to the InCallScreen
		// (or especially to the *activity lifecycle* of the InCallScreen).
		// Instead, the InCallScreen should only be concerned with running the
		// onscreen UI while in a call. So we've now offloaded the call-control
		// functionality to a new module called CallController, and OTASP calls
		// are now launched from the OtaUtils startInteractiveOtasp() or
		// startNonInteractiveOtasp() methods.
		//
		// So now, the InCallScreen is only ever launched using the ACTION_MAIN
		// action, and (upon launch) performs no functionality other than
		// displaying the UI in a state that matches the current telephony
		// state.

		if (action.equals(intent.ACTION_MAIN)) {
			// This action is the normal way to bring up the in-call UI.
			//
			// Most of the interesting work of updating the onscreen UI (to
			// match the current telephony state) happens in the
			// syncWithPhoneState() => updateScreen() sequence that happens in
			// onResume().
			//
			// But we do check here for one extra that can come along with the
			// ACTION_MAIN intent:

			if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
				// SHOW_DIALPAD_EXTRA can be used here to specify whether the
				// DTMF
				// dialpad should be initially visible. If the extra isn't
				// present at all, we just leave the dialpad in its previous
				// state.

				boolean showDialpad = intent.getBooleanExtra(
						SHOW_DIALPAD_EXTRA, false);
				if (VDBG)
					log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: "
							+ showDialpad);

				// If SHOW_DIALPAD_EXTRA is specified, that overrides whatever
				// the previous state of inCallUiState.showDialpad was.
				mApp.inCallUiState.showDialpad = showDialpad;
			}
			// ...and in onResume() we'll update the onscreen dialpad state to
			// match the InCallUiState.

			return;
		}

		if (action.equals(OtaUtils.ACTION_DISPLAY_ACTIVATION_SCREEN)) {
			// Bring up the in-call UI in the OTASP-specific "activate" state;
			// see OtaUtils.startInteractiveOtasp(). Note that at this point
			// the OTASP call has not been started yet; we won't actually make
			// the call until the user presses the "Activate" button.

			if (!TelephonyCapabilities.supportsOtasp(mPhone)) {
				throw new IllegalStateException(
						"Received ACTION_DISPLAY_ACTIVATION_SCREEN intent on non-OTASP-capable device: "
								+ intent);
			}

			setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
			if ((mApp.cdmaOtaProvisionData != null)
					&& (!mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {
				mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;
				mApp.cdmaOtaScreenState.otaScreenState = CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
			}
			return;
		}

		// Various intent actions that should no longer come here directly:
		if (action.equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {
			// This intent is now handled by the InCallScreenShowActivation
			// activity, which translates it into a call to
			// OtaUtils.startInteractiveOtasp().
			throw new IllegalStateException(
					"Unexpected ACTION_PERFORM_CDMA_PROVISIONING received by InCallScreen: "
							+ intent);
		} else if (action.equals(Intent.ACTION_CALL)
				|| action.equals(Intent.ACTION_CALL_EMERGENCY)) {
			// ACTION_CALL* intents go to the OutgoingCallBroadcaster, which now
			// translates them into CallController.placeCall() calls rather than
			// launching the InCallScreen directly.
			throw new IllegalStateException(
					"Unexpected CALL action received by InCallScreen: "
							+ intent);
		} else if (action.equals(ACTION_UNDEFINED)) {
			// This action is only used for internal bookkeeping; we should
			// never actually get launched with it.
			Log.wtf(LOG_TAG,
					"internalResolveIntent: got launched with ACTION_UNDEFINED");
			return;
		} else {
			Log.wtf(LOG_TAG,
					"internalResolveIntent: unexpected intent action: "
							+ action);
			// But continue the best we can (basically treating this case
			// like ACTION_MAIN...)
			return;
		}
	}
拨号到界面显示出来到此就分析完了,但没有涉及到Framework层,后面会再分析Framework;在手机端挂断电话后,拨号界面也会关闭,那么这个过程是怎么走的,下面也来分析下在前面一篇界面了PhoneApp类的初始化,在onCreate()时会对CallNotifier类进行初始化
// 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,使用单例
/**
     * Initialize the singleton CallNotifier instance.
     * This is only done once, at startup, from PhoneApp.onCreate().
     */
    /* package */ static CallNotifier init(PhoneApp app, Phone phone, Ringer ringer,
                                           BluetoothHandsfree btMgr, CallLogAsync callLog) {
        synchronized (CallNotifier.class) {
            if (sInstance == null) {
                sInstance = new CallNotifier(app, phone, ringer, btMgr, callLog);
            } else {
                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
            }
            return sInstance;
        }
    }
在构造函数里做一些初始化工作

/** Private constructor; @see init() */
    private CallNotifier(PhoneApp app, Phone phone, Ringer ringer,
                         BluetoothHandsfree btMgr, CallLogAsync callLog) {
        mApplication = app;
        mCM = app.mCM;
        mCallLog = callLog;

        mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
        //跟CallManager注册通知,跟Framework通