編輯:關於Android編程
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通訊 registerForNotifications(); // Instantiate the ToneGenerator for SignalInfo and CallWaiting // TODO: We probably don't need the mSignalInfoToneGenerator instance // around forever. Need to change it so as to create a ToneGenerator instance only // when a tone is being played and releases it after its done playing. try { mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, TONE_RELATIVE_VOLUME_SIGNALINFO); } catch (RuntimeException e) { Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " + "mSignalInfoToneGenerator: " + e); mSignalInfoToneGenerator = null; } mRinger = ringer; mBluetoothHandsfree = btMgr; TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService( Context.TELEPHONY_SERVICE); telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR); }注冊消息,跟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); }消息處理部分
@Override public void handleMessage(Message msg) { switch (msg.what) { case PHONE_NEW_RINGING_CONNECTION: log("RINGING... (new)"); onNewRingingConnection((AsyncResult) msg.obj); mSilentRingerRequested = false; break; case PHONE_INCOMING_RING: // repeat the ring when requested by the RIL, and when the user has NOT // specifically requested silence. if (msg.obj != null && ((AsyncResult) msg.obj).result != null) { PhoneBase pb = (PhoneBase)((AsyncResult)msg.obj).result; if ((pb.getState() == Phone.State.RINGING) && (mSilentRingerRequested == false)) { if (DBG) log("RINGING... (PHONE_INCOMING_RING event)"); mRinger.ring(); } else { if (DBG) log("RING before NEW_RING, skipping"); } } break; case PHONE_STATE_CHANGED: onPhoneStateChanged((AsyncResult) msg.obj); break; case PHONE_DISCONNECT: if (DBG) log("DISCONNECT"); onDisconnect((AsyncResult) msg.obj); break; case PHONE_UNKNOWN_CONNECTION_APPEARED: onUnknownConnectionAppeared((AsyncResult) msg.obj); break; case RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT: // CallerInfo query is taking too long! But we can't wait // any more, so start ringing NOW even if it means we won't // use the correct custom ringtone. Log.w(LOG_TAG, "CallerInfo query took too long; manually starting ringer"); // In this case we call onCustomRingQueryComplete(), just // like if the query had completed normally. (But we're // going to get the default ringtone, since we never got // the chance to call Ringer.setCustomRingtoneUri()). onCustomRingQueryComplete(); break; case PHONE_MWI_CHANGED: onMwiChanged(mApplication.phone.getMessageWaitingIndicator()); break; case PHONE_BATTERY_LOW: onBatteryLow(); break; case PHONE_CDMA_CALL_WAITING: if (DBG) log("Received PHONE_CDMA_CALL_WAITING event"); onCdmaCallWaiting((AsyncResult) msg.obj); break; case CDMA_CALL_WAITING_REJECT: Log.i(LOG_TAG, "Received CDMA_CALL_WAITING_REJECT event"); onCdmaCallWaitingReject(); break; case CALLWAITING_CALLERINFO_DISPLAY_DONE: Log.i(LOG_TAG, "Received CALLWAITING_CALLERINFO_DISPLAY_DONE event"); mCallWaitingTimeOut = true; onCdmaCallWaitingReject(); break; case CALLWAITING_ADDCALL_DISABLE_TIMEOUT: if (DBG) log("Received CALLWAITING_ADDCALL_DISABLE_TIMEOUT event ..."); // Set the mAddCallMenuStateAfterCW state to true mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true); mApplication.updateInCallScreen(); break; case PHONE_STATE_DISPLAYINFO: if (DBG) log("Received PHONE_STATE_DISPLAYINFO event"); onDisplayInfo((AsyncResult) msg.obj); break; case PHONE_STATE_SIGNALINFO: if (DBG) log("Received PHONE_STATE_SIGNALINFO event"); onSignalInfo((AsyncResult) msg.obj); break; case DISPLAYINFO_NOTIFICATION_DONE: if (DBG) log("Received Display Info notification done event ..."); CdmaDisplayInfo.dismissDisplayInfoRecord(); break; case EVENT_OTA_PROVISION_CHANGE: if (DBG) log("EVENT_OTA_PROVISION_CHANGE..."); mApplication.handleOtaspEvent(msg); break; case PHONE_ENHANCED_VP_ON: if (DBG) log("PHONE_ENHANCED_VP_ON..."); if (!mVoicePrivacyState) { int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY; new InCallTonePlayer(toneToPlay).start(); mVoicePrivacyState = true; // Update the VP icon: if (DBG) log("- updating notification for VP state..."); mApplication.notificationMgr.updateInCallNotification(); } break; case PHONE_ENHANCED_VP_OFF: if (DBG) log("PHONE_ENHANCED_VP_OFF..."); if (mVoicePrivacyState) { int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY; new InCallTonePlayer(toneToPlay).start(); mVoicePrivacyState = false; // Update the VP icon: if (DBG) log("- updating notification for VP state..."); mApplication.notificationMgr.updateInCallNotification(); } break; case PHONE_RINGBACK_TONE: onRingbackTone((AsyncResult) msg.obj); break; case PHONE_RESEND_MUTE: onResendMute(); break; case UPDATE_IN_CALL_NOTIFICATION: mApplication.notificationMgr.updateInCallNotification(); break; default: // super.handleMessage(msg); } }主要分析通話狀態的改變標記:PHONE_STATE_CHANGED
case PHONE_STATE_CHANGED: onPhoneStateChanged((AsyncResult) msg.obj); break;根據不同的狀態處理
/** * Updates the phone UI in response to phone state changes. * * Watch out: certain state changes are actually handled by their own * specific methods: * - see onNewRingingConnection() for new incoming calls * - see onDisconnect() for calls being hung up or disconnected */ private void onPhoneStateChanged(AsyncResult r) { Phone.State state = mCM.getState(); if (VDBG) log("onPhoneStateChanged: state = " + state); // Turn status bar notifications on or off depending upon the state // of the phone. Notification Alerts (audible or vibrating) should // be on if and only if the phone is IDLE. mApplication.notificationMgr.statusBarHelper .enableNotificationAlerts(state == Phone.State.IDLE); Phone fgPhone = mCM.getFgPhone(); if (fgPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) { if ((fgPhone.getForegroundCall().getState() == Call.State.ACTIVE) && ((mPreviousCdmaCallState == Call.State.DIALING) || (mPreviousCdmaCallState == Call.State.ALERTING))) { if (mIsCdmaRedialCall) { int toneToPlay = InCallTonePlayer.TONE_REDIAL; new InCallTonePlayer(toneToPlay).start(); } // Stop any signal info tone when call moves to ACTIVE state stopSignalInfoTone(); } mPreviousCdmaCallState = fgPhone.getForegroundCall().getState(); } // Have the PhoneApp recompute its mShowBluetoothIndication // flag based on the (new) telephony state. // There's no need to force a UI update since we update the // in-call notification ourselves (below), and the InCallScreen // listens for phone state changes itself. mApplication.updateBluetoothIndication(false); // Update the proximity sensor mode (on devices that have a // proximity sensor). mApplication.updatePhoneState(state); if (state == Phone.State.OFFHOOK) { // stop call waiting tone if needed when answering if (mCallWaitingTonePlayer != null) { mCallWaitingTonePlayer.stopTone(); mCallWaitingTonePlayer = null; } if (VDBG) log("onPhoneStateChanged: OFF HOOK"); // make sure audio is in in-call mode now PhoneUtils.setAudioMode(mCM); // if the call screen is showing, let it handle the event, // otherwise handle it here. if (!mApplication.isShowingCallScreen()) { mApplication.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT); mApplication.requestWakeState(PhoneApp.WakeState.SLEEP); } // Since we're now in-call, the Ringer should definitely *not* // be ringing any more. (This is just a sanity-check; we // already stopped the ringer explicitly back in // PhoneUtils.answerCall(), before the call to phone.acceptCall().) // TODO: Confirm that this call really *is* unnecessary, and if so, // remove it! if (DBG) log("stopRing()... (OFFHOOK state)"); mRinger.stopRing(); // Post a request to update the "in-call" status bar icon. // // We don't call NotificationMgr.updateInCallNotification() // directly here, for two reasons: // (1) a single phone state change might actually trigger multiple // onPhoneStateChanged() callbacks, so this prevents redundant // updates of the notification. // (2) we suppress the status bar icon while the in-call UI is // visible (see updateInCallNotification()). But when launching // an outgoing call the phone actually goes OFFHOOK slightly // *before* the InCallScreen comes up, so the delay here avoids a // brief flicker of the icon at that point. if (DBG) log("- posting UPDATE_IN_CALL_NOTIFICATION request..."); // Remove any previous requests in the queue removeMessages(UPDATE_IN_CALL_NOTIFICATION); final int IN_CALL_NOTIFICATION_UPDATE_DELAY = 1000; // msec sendEmptyMessageDelayed(UPDATE_IN_CALL_NOTIFICATION, IN_CALL_NOTIFICATION_UPDATE_DELAY); } if (fgPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) { Connection c = fgPhone.getForegroundCall().getLatestConnection(); if ((c != null) && (PhoneNumberUtils.isLocalEmergencyNumber(c.getAddress(), mApplication))) { if (VDBG) log("onPhoneStateChanged: it is an emergency call."); Call.State callState = fgPhone.getForegroundCall().getState(); if (mEmergencyTonePlayerVibrator == null) { mEmergencyTonePlayerVibrator = new EmergencyTonePlayerVibrator(); } if (callState == Call.State.DIALING || callState == Call.State.ALERTING) { mIsEmergencyToneOn = Settings.System.getInt( mApplication.getContentResolver(), Settings.System.EMERGENCY_TONE, EMERGENCY_TONE_OFF); if (mIsEmergencyToneOn != EMERGENCY_TONE_OFF && mCurrentEmergencyToneState == EMERGENCY_TONE_OFF) { if (mEmergencyTonePlayerVibrator != null) { mEmergencyTonePlayerVibrator.start(); } } } else if (callState == Call.State.ACTIVE) { if (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF) { if (mEmergencyTonePlayerVibrator != null) { mEmergencyTonePlayerVibrator.stop(); } } } } } if ((fgPhone.getPhoneType() == Phone.PHONE_TYPE_GSM) || (fgPhone.getPhoneType() == Phone.PHONE_TYPE_SIP)) { Call.State callState = mCM.getActiveFgCallState(); if (!callState.isDialing()) { // If call get activated or disconnected before the ringback // tone stops, we have to stop it to prevent disturbing. if (mInCallRingbackTonePlayer != null) { mInCallRingbackTonePlayer.stopTone(); mInCallRingbackTonePlayer = null; } } } }如果需要對不能的通話狀態處理不同的事情可以獲取以下狀態
Phone.State.OFFHOOK 掛斷
Phone.State.RINGING 正在來電鈴聲狀態
Phone.State.IDLE 空間狀態
例如在通過結束後關閉InCallScreen界面可以添加以下代碼
if(Phone.State.OFFHOOK == state){ finish(); }後面再分析InCallScreen的UI顯示和Framework層
Touch事件分發中只有兩個主角:ViewGroup和View。Activity的Touch事件事實上是調用它內部的ViewGroup的Touch事件,可以直接當成Vie
今天來聊聊,android中接入微信支付的需求,肯定有人會說,這多簡單呀,還在這裡扯什麼,趕快去洗洗睡吧~~ 那我就不服了,要是說這簡單的,你知道微信支付官網多少嗎,要
本節引言: 本節給大家帶了的是ViewFlipper,它是Android自帶的一個多頁面管理控件,且可以自動播放! 和ViewPager不同,ViewPage
最近群裡有人問如何在mac下進行apk反編譯,我也沒試過,以前都是在windows下進行反編譯的,windows下很簡單,有許多比較好的集成工具,如apkide(改之理)