blob: 3a444c0521677845f635486a39d594fbf47b4735 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2023 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.incallui;
import androidx.annotation.NonNull;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
import com.android.incallui.call.CallList;
import com.android.incallui.call.DialerCall;
import com.android.incallui.call.state.DialerCallState;
import java.util.Objects;
/**
* This class is responsible for generating video pause/resume requests when the InCall UI is sent
* to the background and subsequently brought back to the foreground.
*/
class VideoPauseController implements InCallStateListener, IncomingCallListener {
private static VideoPauseController videoPauseController;
private InCallPresenter inCallPresenter;
/** The current call, if applicable. */
private DialerCall primaryCall = null;
/**
* The cached state of primary call, updated after onStateChange has processed.
*
* <p>These values are stored to detect specific changes in state between onStateChange calls.
*/
private int prevCallState = DialerCallState.INVALID;
private boolean wasVideoCall = false;
/**
* Tracks whether the application is in the background. {@code True} if the application is in the
* background, {@code false} otherwise.
*/
private boolean isInBackground = false;
/**
* Singleton accessor for the {@link VideoPauseController}.
*
* @return Singleton instance of the {@link VideoPauseController}.
*/
/*package*/
static synchronized VideoPauseController getInstance() {
if (videoPauseController == null) {
videoPauseController = new VideoPauseController();
}
return videoPauseController;
}
/**
* Determines if a call is in incoming/waiting state.
*
* @param call The call.
* @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
*/
private static boolean isIncomingCall(DialerCall call) {
return call != null
&& (call.getState() == DialerCallState.CALL_WAITING
|| call.getState() == DialerCallState.INCOMING);
}
/**
* Determines if a call is dialing.
*
* @return {@code true} if the call is dialing, {@code false} otherwise.
*/
private boolean wasDialing() {
return DialerCallState.isDialing(prevCallState);
}
/**
* Configures the {@link VideoPauseController} to listen to call events. Configured via the {@link
* com.android.incallui.InCallPresenter}.
*
* @param inCallPresenter The {@link com.android.incallui.InCallPresenter}.
*/
public void setUp(@NonNull InCallPresenter inCallPresenter) {
LogUtil.enterBlock("VideoPauseController.setUp");
this.inCallPresenter = Assert.isNotNull(inCallPresenter);
this.inCallPresenter.addListener(this);
this.inCallPresenter.addIncomingCallListener(this);
}
/**
* Cleans up the {@link VideoPauseController} by removing all listeners and clearing its internal
* state. Called from {@link com.android.incallui.InCallPresenter}.
*/
public void tearDown() {
LogUtil.enterBlock("VideoPauseController.tearDown");
inCallPresenter.removeListener(this);
inCallPresenter.removeIncomingCallListener(this);
clear();
}
/** Clears the internal state for the {@link VideoPauseController}. */
private void clear() {
inCallPresenter = null;
primaryCall = null;
prevCallState = DialerCallState.INVALID;
wasVideoCall = false;
isInBackground = false;
}
/**
* Handles changes in the {@link InCallState}. Triggers pause and resumption of video for the
* current foreground call.
*
* @param oldState The previous {@link InCallState}.
* @param newState The current {@link InCallState}.
* @param callList List of current call.
*/
@Override
public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
DialerCall call;
if (newState == InCallState.INCOMING) {
call = callList.getIncomingCall();
} else if (newState == InCallState.WAITING_FOR_ACCOUNT) {
call = callList.getWaitingForAccountCall();
} else if (newState == InCallState.PENDING_OUTGOING) {
call = callList.getPendingOutgoingCall();
} else if (newState == InCallState.OUTGOING) {
call = callList.getOutgoingCall();
} else {
call = callList.getActiveCall();
}
boolean hasPrimaryCallChanged = !Objects.equals(call, primaryCall);
boolean canVideoPause = videoCanPause(call);
LogUtil.i(
"VideoPauseController.onStateChange",
"hasPrimaryCallChanged: %b, videoCanPause: %b, isInBackground: %b",
hasPrimaryCallChanged,
canVideoPause,
isInBackground);
if (hasPrimaryCallChanged) {
onPrimaryCallChanged(call);
return;
}
if (wasDialing() && canVideoPause && isInBackground) {
// Bring UI to foreground if outgoing request becomes active while UI is in
// background.
bringToForeground();
} else if (!wasVideoCall && canVideoPause && isInBackground) {
// Bring UI to foreground if VoLTE call becomes active while UI is in
// background.
bringToForeground();
}
updatePrimaryCallContext(call);
}
/**
* Handles a change to the primary call.
*
* <p>Reject incoming or hangup dialing call: Where the previous call was an incoming call or a
* call in dialing state, resume the new primary call. DialerCall swap: Where the new primary call
* is incoming, pause video on the previous primary call.
*
* @param call The new primary call.
*/
private void onPrimaryCallChanged(DialerCall call) {
LogUtil.i(
"VideoPauseController.onPrimaryCallChanged",
"new call: %s, old call: %s, mIsInBackground: %b",
call,
primaryCall,
isInBackground);
if (Objects.equals(call, primaryCall)) {
throw new IllegalStateException();
}
final boolean canVideoPause = videoCanPause(call);
if (canVideoPause && !isInBackground) {
// Send resume request for the active call, if user rejects incoming call, ends dialing
// call, or the call was previously in a paused state and UI is in the foreground.
sendRequest(call, true);
} else if (isIncomingCall(call) && videoCanPause(primaryCall)) {
// Send pause request if there is an active video call, and we just received a new
// incoming call.
sendRequest(primaryCall, false);
}
updatePrimaryCallContext(call);
}
/**
* Handles new incoming calls by triggering a change in the primary call.
*
* @param oldState the old {@link InCallState}.
* @param newState the new {@link InCallState}.
* @param call the incoming call.
*/
@Override
public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
LogUtil.i(
"VideoPauseController.onIncomingCall",
"oldState: %s, newState: %s, call: %s",
oldState,
newState,
call);
if (Objects.equals(call, primaryCall)) {
return;
}
onPrimaryCallChanged(call);
}
/**
* Caches a reference to the primary call and stores its previous state.
*
* @param call The new primary call.
*/
private void updatePrimaryCallContext(DialerCall call) {
if (call == null) {
primaryCall = null;
prevCallState = DialerCallState.INVALID;
wasVideoCall = false;
} else {
primaryCall = call;
prevCallState = call.getState();
wasVideoCall = call.isVideoCall();
}
}
/**
* Called when UI goes in/out of the foreground.
*
* @param showing true if UI is in the foreground, false otherwise.
*/
public void onUiShowing(boolean showing) {
if (inCallPresenter == null) {
return;
}
final boolean isInCall = inCallPresenter.getInCallState() == InCallState.INCALL;
if (showing) {
onResume(isInCall);
} else {
onPause(isInCall);
}
}
/**
* Called when UI is brought to the foreground. Sends a session modification request to resume the
* outgoing video.
*
* @param isInCall {@code true} if we are in an active call. A resume request is only sent to the
* video provider if we are in a call.
*/
private void onResume(boolean isInCall) {
isInBackground = false;
if (isInCall) {
sendRequest(primaryCall, true);
}
}
/**
* Called when UI is sent to the background. Sends a session modification request to pause the
* outgoing video.
*
* @param isInCall {@code true} if we are in an active call. A pause request is only sent to the
* video provider if we are in a call.
*/
private void onPause(boolean isInCall) {
isInBackground = true;
if (isInCall) {
sendRequest(primaryCall, false);
}
}
private void bringToForeground() {
LogUtil.enterBlock("VideoPauseController.bringToForeground");
if (inCallPresenter != null) {
inCallPresenter.bringToForeground(false);
} else {
LogUtil.e(
"VideoPauseController.bringToForeground",
"InCallPresenter is null. Cannot bring UI to foreground");
}
}
/**
* Sends Pause/Resume request.
*
* @param call DialerCall to be paused/resumed.
* @param resume If true resume request will be sent, otherwise pause request.
*/
private void sendRequest(DialerCall call, boolean resume) {
if (call == null) {
return;
}
if (resume) {
call.getVideoTech().unpause();
} else {
call.getVideoTech().pause();
}
}
private static boolean videoCanPause(DialerCall call) {
return call != null && call.isVideoCall() && call.getState() == DialerCallState.ACTIVE;
}
}