@Override public void onBackPressed() { Log.i(this, "onBackPressed"); // BACK is also used to exit out of any "special modes" of the // in-call UI: if (!mConferenceManagerFragment.isVisible() && !mCallCardFragment.isVisible()) { return; } if (mDialpadFragment != null && mDialpadFragment.isVisible()) { mCallButtonFragment.displayDialpad(false /* show */, true /* animate */); return; } else if (mConferenceManagerFragment.isVisible()) { showConferenceCallManager(false); return; } // Always disable the Back key while an incoming call is ringing final Call call = CallList.getInstance().getIncomingCall(); if (call != null) { Log.d(this, "Consume Back press for an incoming call"); return; } // Nothing special to do. Fall back to the default behavior. super.onBackPressed(); }
/** * Called when there is a change to the call list. Sets the In-Call state for the entire in-call * app based on the information it gets from CallList. Dispatches the in-call state to all * listeners. Can trigger the creation or destruction of the UI based on the states that is * calculates. */ @Override public void onCallListChange(CallList callList) { if (callList == null) { return; } InCallState newState = getPotentialStateFromCallList(callList); newState = startOrFinishUi(newState); // Renable notification shade and soft navigation buttons, if we are no longer in the // incoming call screen if (!newState.isIncoming()) { if (mAccelerometerListener != null) { mAccelerometerListener.enableSensor(false); } CallCommandClient.getInstance().setSystemBarNavigationEnabled(true); } // Set the new state before announcing it to the world Log.i(this, "Phone switching state: " + mInCallState + " -> " + newState); mInCallState = newState; // notify listeners of new state for (InCallStateListener listener : mListeners) { Log.d(this, "Notify " + listener + " of state " + mInCallState.toString()); listener.onStateChange(mInCallState, callList); } if (isActivityStarted()) { final boolean hasCall = callList.getActiveOrBackgroundCall() != null || callList.getOutgoingCall() != null; mInCallActivity.dismissKeyguard(hasCall); } }
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final CallList calls = CallList.getInstance(); final Call call = calls.getFirstCall(); getPresenter().init(getActivity(), call); }
private void startUi(InCallState inCallState) { final Call incomingCall = mCallList.getIncomingCall(); final boolean isCallWaiting = (incomingCall != null && incomingCall.getState() == Call.State.CALL_WAITING); // If the screen is off, we need to make sure it gets turned on for incoming calls. // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works // when the activity is first created. Therefore, to ensure the screen is turned on // for the call waiting case, we finish() the current activity and start a new one. // There should be no jank from this since the screen is already off and will remain so // until our new activity is up. if (mProximitySensor.isScreenReallyOff() && isCallWaiting) { if (isActivityStarted()) { mInCallActivity.finish(); } mInCallActivity = null; } final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); // If the screen is on, we'll prefer to not interrupt the user too much and slide in a card if (pm.isScreenOn()) { Intent intent = new Intent(mContext, InCallCardActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } else { mStatusBarNotifier.updateNotificationAndLaunchIncomingCallUi(inCallState, mCallList); } }
@Override public int hashCode() { int result = lastDateUpdate != null ? lastDateUpdate.hashCode() : 0; result = 31 * result + (callList != null ? callList.hashCode() : 0); result = 31 * result + (status != null ? status.hashCode() : 0); result = 31 * result + (user != null ? user.hashCode() : 0); result = 31 * result + (company != null ? company.hashCode() : 0); result = 31 * result + (event != null ? event.hashCode() : 0); return result; }
/** Hangs up any active or outgoing calls. */ public void hangUpOngoingCall(Context context) { // By the time we receive this intent, we could be shut down and call list // could be null. Bail in those cases. if (mCallList == null) { if (mStatusBarNotifier == null) { // The In Call UI has crashed but the notification still stayed up. We should not // come to this stage. StatusBarNotifier.clearInCallNotification(context); } return; } Call call = mCallList.getOutgoingCall(); if (call == null) { call = mCallList.getActiveOrBackgroundCall(); } if (call != null) { CallCommandClient.getInstance().disconnectCall(call.getCallId()); } }
/** * Called when a call becomes disconnected. Called everytime an existing call changes from being * connected (incoming/outgoing/active) to disconnected. */ @Override public void onDisconnect(Call call) { hideDialpadForDisconnect(); maybeShowErrorDialogOnDisconnect(call); // We need to do the run the same code as onCallListChange. onCallListChange(CallList.getInstance()); if (isActivityStarted()) { mInCallActivity.dismissKeyguard(false); } }
private void relaunchedFromDialer(boolean showDialpad) { mShowDialpadRequested = showDialpad; mAnimateDialpadOnShow = true; if (mShowDialpadRequested) { // If there's only one line in use, AND it's on hold, then we're sure the user // wants to use the dialpad toward the exact line, so un-hold the holding line. final Call call = CallList.getInstance().getActiveOrBackgroundCall(); if (call != null && call.getState() == State.ONHOLD) { TelecomAdapter.getInstance().unholdCall(call.getId()); } } }
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof CallListCompany)) return false; CallListCompany that = (CallListCompany) o; if (callList != null ? !callList.equals(that.callList) : that.callList != null) return false; if (company != null ? !company.equals(that.company) : that.company != null) return false; if (event != null ? !event.equals(that.event) : that.event != null) return false; if (lastDateUpdate != null ? !lastDateUpdate.equals(that.lastDateUpdate) : that.lastDateUpdate != null) return false; if (status != null ? !status.equals(that.status) : that.status != null) return false; if (user != null ? !user.equals(that.user) : that.user != null) return false; return true; }
/** * Checks to see if both the UI is gone and the service is disconnected. If so, tear it all down. */ private void attemptCleanup() { boolean shouldCleanup = (mInCallActivity == null && !mServiceConnected && mInCallState == InCallState.NO_CALLS); Log.i(this, "attemptCleanup? " + shouldCleanup); if (shouldCleanup) { mIsActivityPreviouslyStarted = false; // blow away stale contact info so that we get fresh data on // the next set of calls if (mContactInfoCache != null) { mContactInfoCache.clearCache(); } mContactInfoCache = null; if (mProximitySensor != null) { removeListener(mProximitySensor); mProximitySensor.tearDown(); } mProximitySensor = null; mAccelerometerListener = null; mAudioModeProvider = null; if (mStatusBarNotifier != null) { removeListener(mStatusBarNotifier); } mStatusBarNotifier = null; if (mCallList != null) { mCallList.removeListener(this); } mCallList = null; mContext = null; mInCallActivity = null; mListeners.clear(); mIncomingCallListeners.clear(); Log.d(this, "Finished InCallPresenter.CleanUp"); } }
public void setUp(Context context, CallList callList, AudioModeProvider audioModeProvider) { if (mServiceConnected) { Log.i(this, "New service connection replacing existing one."); // retain the current resources, no need to create new ones. Preconditions.checkState(context == mContext); Preconditions.checkState(callList == mCallList); Preconditions.checkState(audioModeProvider == mAudioModeProvider); return; } Preconditions.checkNotNull(context); mContext = context; mContactInfoCache = ContactInfoCache.getInstance(context); mStatusBarNotifier = new StatusBarNotifier(context, mContactInfoCache); addListener(mStatusBarNotifier); mAudioModeProvider = audioModeProvider; mProximitySensor = new ProximitySensor(context, mAudioModeProvider); addListener(mProximitySensor); mAccelerometerListener = new AccelerometerListener(context); mCallList = callList; // This only gets called by the service so this is okay. mServiceConnected = true; // The final thing we do in this set up is add ourselves as a listener to CallList. This // will kick off an update and the whole process can start. mCallList.addListener(this); Log.d(this, "Finished InCallPresenter.setUp"); }
/** Given the call list, return the state in which the in-call screen should be. */ public static InCallState getPotentialStateFromCallList(CallList callList) { InCallState newState = InCallState.NO_CALLS; if (callList == null) { return newState; } if (callList.getIncomingCall() != null) { newState = InCallState.INCOMING; } else if (callList.getOutgoingCall() != null) { newState = InCallState.OUTGOING; } else if (callList.getActiveCall() != null || callList.getBackgroundCall() != null || callList.getDisconnectedCall() != null || callList.getDisconnectingCall() != null) { newState = InCallState.INCALL; } return newState; }
private void internalResolveIntent(Intent intent) { final String action = intent.getAction(); if (action.equals(intent.ACTION_MAIN)) { // This action is the normal way to bring up the in-call UI. // // 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. final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false); Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad); relaunchedFromDialer(showDialpad); } if (intent.getBooleanExtra(NEW_OUTGOING_CALL_EXTRA, false)) { intent.removeExtra(NEW_OUTGOING_CALL_EXTRA); Call call = CallList.getInstance().getOutgoingCall(); if (call == null) { call = CallList.getInstance().getPendingOutgoingCall(); } Bundle extras = null; if (call != null) { extras = call.getTelecommCall().getDetails().getExtras(); } if (extras == null) { // Initialize the extras bundle to avoid NPE extras = new Bundle(); } Point touchPoint = null; if (TouchPointManager.getInstance().hasValidPoint()) { // Use the most immediate touch point in the InCallUi if available touchPoint = TouchPointManager.getInstance().getPoint(); } else { // Otherwise retrieve the touch point from the call intent if (call != null) { touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT); } } // This is only true in the case where an outgoing call is initiated by tapping // on the "Select account dialog", in which case we skip the initial animation. In // most other cases the circular reveal is done by OutgoingCallAnimationActivity. final boolean showCircularReveal = intent.getBooleanExtra(SHOW_CIRCULAR_REVEAL_EXTRA, false); mCallCardFragment.animateForNewOutgoingCall(touchPoint, showCircularReveal); // InCallActivity is responsible for disconnecting a new outgoing call if there // is no way of making it (i.e. no valid call capable accounts) if (InCallPresenter.isCallWithNoValidAccounts(call)) { TelecomAdapter.getInstance().disconnectCall(call.getId()); } dismissKeyguard(true); } Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall(); if (pendingAccountSelectionCall != null) { mCallCardFragment.setVisible(false); Bundle extras = pendingAccountSelectionCall.getTelecommCall().getDetails().getExtras(); final List<PhoneAccountHandle> phoneAccountHandles; if (extras != null) { phoneAccountHandles = extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); } else { phoneAccountHandles = new ArrayList<>(); } SelectPhoneAccountListener listener = new SelectPhoneAccountListener() { @Override public void onPhoneAccountSelected( PhoneAccountHandle selectedAccountHandle, boolean setDefault) { InCallPresenter.getInstance() .handleAccountSelection(selectedAccountHandle, setDefault); } @Override public void onDialogDismissed() { InCallPresenter.getInstance().cancelAccountSelection(); } }; SelectPhoneAccountDialogFragment.showAccountDialog( getFragmentManager(), R.string.select_phone_account_for_calls, true, phoneAccountHandles, listener); } else { mCallCardFragment.setVisible(true); } return; } }
/** * Handles the green CALL key while in-call. * * @return true if we consumed the event. */ public boolean handleCallKey() { Log.v(this, "handleCallKey"); // The green CALL button means either "Answer", "Unhold", or // "Swap calls", or can be a no-op, depending on the current state // of the Phone. /** INCOMING CALL */ final CallList calls = CallList.getInstance(); final Call incomingCall = calls.getIncomingCall(); Log.v(this, "incomingCall: " + incomingCall); // (1) Attempt to answer a call if (incomingCall != null) { CallCommandClient.getInstance().answerCall(incomingCall.getCallId()); if (mAccelerometerListener != null) { mAccelerometerListener.enableSensor(false); } return true; } /** ACTIVE CALL */ final Call activeCall = calls.getActiveCall(); if (activeCall != null) { // TODO: This logic is repeated from CallButtonPresenter.java. We should // consolidate this logic. final boolean isGeneric = activeCall.can(Capabilities.GENERIC_CONFERENCE); final boolean canMerge = activeCall.can(Capabilities.MERGE_CALLS); final boolean canSwap = activeCall.can(Capabilities.SWAP_CALLS); Log.v( this, "activeCall: " + activeCall + ", isGeneric: " + isGeneric + ", canMerge: " + canMerge + ", canSwap: " + canSwap); // (2) Attempt actions on Generic conference calls if (activeCall.isConferenceCall() && isGeneric) { if (canMerge) { CallCommandClient.getInstance().merge(); return true; } else if (canSwap) { CallCommandClient.getInstance().swap(); return true; } } // (3) Swap calls if (canSwap) { CallCommandClient.getInstance().swap(); return true; } } /** BACKGROUND CALL */ final Call heldCall = calls.getBackgroundCall(); if (heldCall != null) { // We have a hold call so presumeable it will always support HOLD...but // there is no harm in double checking. final boolean canHold = heldCall.can(Capabilities.HOLD); Log.v(this, "heldCall: " + heldCall + ", canHold: " + canHold); // (4) unhold call if (heldCall.getState() == Call.State.ONHOLD && canHold) { CallCommandClient.getInstance().hold(heldCall.getCallId(), false); return true; } } // Always consume hard keys return true; }
/** * 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(); } }