/** * A dialog could have prevented in-call screen from being previously finished. This function * checks to see if there should be any UI left and if not attempts to tear down the UI. */ public void onDismissDialog() { Log.i(this, "Dialog dismissed"); if (mInCallState == InCallState.NO_CALLS) { attemptFinishActivity(); attemptCleanup(); } }
/** * When the state of in-call changes, this is the first method to get called. It determines if the * UI needs to be started or finished depending on the new state and does it. */ private InCallState startOrFinishUi(InCallState newState) { Log.d(this, "startOrFinishUi: " + mInCallState + " -> " + newState); // TODO: Consider a proper state machine implementation // If the state isn't changing, we have already done any starting/stopping of // activities in a previous pass...so lets cut out early if (newState == mInCallState) { return newState; } // A new Incoming call means that the user needs to be notified of the the call (since // it wasn't them who initiated it). We do this through full screen notifications and // happens indirectly through {@link StatusBarListener}. // // The process for incoming calls is as follows: // // 1) CallList - Announces existence of new INCOMING call // 2) InCallPresenter - Gets announcement and calculates that the new InCallState // - should be set to INCOMING. // 3) InCallPresenter - This method is called to see if we need to start or finish // the app given the new state. // 4) StatusBarNotifier - Listens to InCallState changes. InCallPresenter calls // StatusBarNotifier explicitly to issue a FullScreen Notification // that will either start the InCallActivity or show the user a // top-level notification dialog if the user is in an immersive app. // That notification can also start the InCallActivity. // 5) InCallActivity - Main activity starts up and at the end of its onCreate will // call InCallPresenter::setActivity() to let the presenter // know that start-up is complete. // // [ AND NOW YOU'RE IN THE CALL. voila! ] // // Our app is started using a fullScreen notification. We need to do this whenever // we get an incoming call. final boolean startStartupSequence = (InCallState.INCOMING == newState); // A new outgoing call indicates that the user just now dialed a number and when that // happens we need to display the screen immediateley. // // This is different from the incoming call sequence because we do not need to shock the // user with a top-level notification. Just show the call UI normally. final boolean showCallUi = (InCallState.OUTGOING == newState); // TODO: Can we be suddenly in a call without it having been in the outgoing or incoming // state? I havent seen that but if it can happen, the code below should be enabled. // showCallUi |= (InCallState.INCALL && !isActivityStarted()); // The only time that we have an instance of mInCallActivity and it isn't started is // when it is being destroyed. In that case, lets avoid bringing up another instance of // the activity. When it is finally destroyed, we double check if we should bring it back // up so we aren't going to lose anything by avoiding a second startup here. boolean activityIsFinishing = mInCallActivity != null && !isActivityStarted(); if (activityIsFinishing) { Log.i(this, "Undo the state change: " + newState + " -> " + mInCallState); return mInCallState; } if (showCallUi) { Log.i(this, "Start in call UI"); showInCall(false); } else if (startStartupSequence) { Log.i(this, "Start Full Screen in call UI"); // We're about the bring up the in-call UI for an incoming call. If we still have // dialogs up, we need to clear them out before showing incoming screen. if (isActivityStarted()) { mInCallActivity.dismissPendingDialogs(); } startUi(newState); } else if (newState == InCallState.NO_CALLS) { // The new state is the no calls state. Tear everything down. attemptFinishActivity(); attemptCleanup(); } return newState; }
/** * Called when the UI begins or ends. Starts the callstate callbacks if the UI just began. * Attempts to tear down everything if the UI just ended. See #tearDown for more insight on the * tear-down process. */ public void setActivity(InCallActivity inCallActivity) { boolean updateListeners = false; boolean doAttemptCleanup = false; if (inCallActivity != null) { if (mInCallActivity == null) { updateListeners = true; Log.i(this, "UI Initialized"); } else if (mInCallActivity != inCallActivity) { Log.wtf(this, "Setting a second activity before destroying the first."); } else { // since setActivity is called onStart(), it can be called multiple times. // This is fine and ignorable, but we do not want to update the world every time // this happens (like going to/from background) so we do not set updateListeners. } mInCallActivity = inCallActivity; // By the time the UI finally comes up, the call may already be disconnected. // If that's the case, we may need to show an error dialog. if (mCallList != null && mCallList.getDisconnectedCall() != null) { maybeShowErrorDialogOnDisconnect(mCallList.getDisconnectedCall()); } // When the UI comes up, we need to first check the in-call state. // If we are showing NO_CALLS, that means that a call probably connected and // then immediately disconnected before the UI was able to come up. // If we dont have any calls, start tearing down the UI instead. // NOTE: This code relies on {@link #mInCallActivity} being set so we run it after // it has been set. if (mInCallState == InCallState.NO_CALLS) { Log.i(this, "UI Intialized, but no calls left. shut down."); attemptFinishActivity(); return; } } else { Log.i(this, "UI Destroyed)"); updateListeners = true; mInCallActivity = null; // We attempt cleanup for the destroy case but only after we recalculate the state // to see if we need to come back up or stay shut down. This is why we do the cleanup // after the call to onCallListChange() instead of directly here. doAttemptCleanup = true; } // Messages can come from the telephony layer while the activity is coming up // and while the activity is going down. So in both cases we need to recalculate what // state we should be in after they complete. // Examples: (1) A new incoming call could come in and then get disconnected before // the activity is created. // (2) All calls could disconnect and then get a new incoming call before the // activity is destroyed. // // b/1122139 - We previously had a check for mServiceConnected here as well, but there are // cases where we need to recalculate the current state even if the service in not // connected. In particular the case where startOrFinish() is called while the app is // already finish()ing. In that case, we skip updating the state with the knowledge that // we will check again once the activity has finished. That means we have to recalculate the // state here even if the service is disconnected since we may not have finished a state // transition while finish()ing. if (updateListeners) { onCallListChange(mCallList); } if (doAttemptCleanup) { attemptCleanup(); } }
/** * Called when the telephony service has disconnected from us. This will happen when there are no * more active calls. However, we may still want to continue showing the UI for certain cases like * showing "Call Ended". What we really want is to wait for the activity and the service to both * disconnect before we tear things down. This method sets a serviceConnected boolean and calls a * secondary method that performs the aforementioned logic. */ public void tearDown() { Log.d(this, "tearDown"); mServiceConnected = false; attemptCleanup(); }