blob: 54e31f8d9da96a5dfaccda702a35550b5b386def [file] [log] [blame]
/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.android.incallui;
import android.telecom.VideoProfile;
import com.android.incallui.Call.State;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
import com.android.incallui.InCallVideoCallCallbackNotifier.SessionModificationListener;
import com.google.common.base.Preconditions;
/**
* 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,
SessionModificationListener {
private static final String TAG = "VideoPauseController:";
/**
* Keeps track of the current active/foreground call.
*/
private class CallContext {
public CallContext(Call call) {
Preconditions.checkNotNull(call);
update(call);
}
public void update(Call call) {
mCall = Preconditions.checkNotNull(call);
mState = call.getState();
mVideoState = call.getVideoState();
}
public int getState() {
return mState;
}
public int getVideoState() {
return mVideoState;
}
public String toString() {
return String.format("CallContext {CallId=%s, State=%s, VideoState=%d}",
mCall.getId(), mState, mVideoState);
}
public Call getCall() {
return mCall;
}
private int mState = State.INVALID;
private int mVideoState;
private Call mCall;
}
private InCallPresenter mInCallPresenter;
private static VideoPauseController sVideoPauseController;
/**
* The current call context, if applicable.
*/
private CallContext mPrimaryCallContext = null;
/**
* Tracks whether the application is in the background. {@code True} if the application is in
* the background, {@code false} otherwise.
*/
private boolean mIsInBackground = false;
/**
* Singleton accessor for the {@link VideoPauseController}.
* @return Singleton instance of the {@link VideoPauseController}.
*/
/*package*/
static synchronized VideoPauseController getInstance() {
if (sVideoPauseController == null) {
sVideoPauseController = new VideoPauseController();
}
return sVideoPauseController;
}
/**
* 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(InCallPresenter inCallPresenter) {
log("setUp");
mInCallPresenter = Preconditions.checkNotNull(inCallPresenter);
mInCallPresenter.addListener(this);
mInCallPresenter.addIncomingCallListener(this);
InCallVideoCallCallbackNotifier.getInstance().addSessionModificationListener(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() {
log("tearDown...");
InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this);
mInCallPresenter.removeListener(this);
mInCallPresenter.removeIncomingCallListener(this);
clear();
}
/**
* Clears the internal state for the {@link VideoPauseController}.
*/
private void clear() {
mInCallPresenter = null;
mPrimaryCallContext = null;
mIsInBackground = 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) {
log("onStateChange, OldState=" + oldState + " NewState=" + newState);
Call call = null;
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 = !areSame(call, mPrimaryCallContext);
boolean canVideoPause = CallUtils.canVideoPause(call);
log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged);
log("onStateChange, canVideoPause=" + canVideoPause);
log("onStateChange, IsInBackground=" + mIsInBackground);
if (hasPrimaryCallChanged) {
onPrimaryCallChanged(call);
return;
}
if (isDialing(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
// Bring UI to foreground if outgoing request becomes active while UI is in
// background.
bringToForeground();
} else if (!isVideoCall(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
// 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.
* Call swap: Where the new primary call is incoming, pause video on the previous primary call.
*
* @param call The new primary call.
*/
private void onPrimaryCallChanged(Call call) {
log("onPrimaryCallChanged: New call = " + call);
log("onPrimaryCallChanged: Old call = " + mPrimaryCallContext);
log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground);
Preconditions.checkState(!areSame(call, mPrimaryCallContext));
final boolean canVideoPause = CallUtils.canVideoPause(call);
if ((isIncomingCall(mPrimaryCallContext) || isDialing(mPrimaryCallContext))
&& canVideoPause && !mIsInBackground) {
// Send resume request for the active call, if user rejects incoming call or ends
// dialing call and UI is in the foreground.
sendRequest(call, true);
} else if (isIncomingCall(call) && canVideoPause(mPrimaryCallContext)) {
// Send pause request if there is an active video call, and we just received a new
// incoming call.
sendRequest(mPrimaryCallContext.getCall(), 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, Call call) {
log("onIncomingCall, OldState=" + oldState + " NewState=" + newState + " Call=" + call);
if (areSame(call, mPrimaryCallContext)) {
return;
}
onPrimaryCallChanged(call);
}
/**
* Caches a reference to the primary call and stores its previous state.
*
* @param call The new primary call.
*/
private void updatePrimaryCallContext(Call call) {
if (call == null) {
mPrimaryCallContext = null;
} else if (mPrimaryCallContext != null) {
mPrimaryCallContext.update(call);
} else {
mPrimaryCallContext = new CallContext(call);
}
}
/**
* 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) {
// Only send pause/unpause requests if we are in the INCALL state.
if (mInCallPresenter == null || mInCallPresenter.getInCallState() != InCallState.INCALL) {
return;
}
if (showing) {
onResume();
} else {
onPause();
}
}
/**
* Handles requests to upgrade to video.
*
* @param call The call the request was received for.
* @param videoState The video state that the request wants to upgrade to.
*/
@Override
public void onUpgradeToVideoRequest(Call call, int videoState) {
// Not used.
}
/**
* Handles successful upgrades to video.
* @param call The call the request was successful for.
*/
@Override
public void onUpgradeToVideoSuccess(Call call) {
// Not used.
}
/**
* Handles a failure to upgrade a call to video.
*
* @param status The failure status.
* @param call The call the request was successful for.
*/
@Override
public void onUpgradeToVideoFail(int status, Call call) {
// TODO (ims-vt) Automatically bring in call ui to foreground.
}
/**
* Handles a downgrade of a call to audio-only.
*
* @param call The call which was downgraded to audio-only.
*/
@Override
public void onDowngradeToAudio(Call call) {
}
/**
* Called when UI is brought to the foreground. Sends a session modification request to resume
* the outgoing video.
*/
private void onResume() {
log("onResume");
mIsInBackground = false;
if (canVideoPause(mPrimaryCallContext)) {
sendRequest(mPrimaryCallContext.getCall(), true);
} else {
log("onResume. Ignoring...");
}
}
/**
* Called when UI is sent to the background. Sends a session modification request to pause the
* outgoing video.
*/
private void onPause() {
log("onPause");
mIsInBackground = true;
if (canVideoPause(mPrimaryCallContext)) {
sendRequest(mPrimaryCallContext.getCall(), false);
} else {
log("onPause, Ignoring...");
}
}
private void bringToForeground() {
if (mInCallPresenter != null) {
log("Bringing UI to foreground");
mInCallPresenter.bringToForeground(false);
} else {
loge("InCallPresenter is null. Cannot bring UI to foreground");
}
}
/**
* Sends Pause/Resume request.
*
* @param call Call to be paused/resumed.
* @param resume If true resume request will be sent, otherwise pause request.
*/
private void sendRequest(Call call, boolean resume) {
// Check if this call supports pause/un-pause.
if (!call.can(android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO)) {
return;
}
if (resume) {
log("sending resume request, call=" + call);
call.getVideoCall()
.sendSessionModifyRequest(CallUtils.makeVideoUnPauseProfile(call));
} else {
log("sending pause request, call=" + call);
call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoPauseProfile(call));
}
}
/**
* Determines if a given call is the same one stored in a {@link CallContext}.
*
* @param call The call.
* @param callContext The call context.
* @return {@code true} if the {@link Call} is the same as the one referenced in the
* {@link CallContext}.
*/
private static boolean areSame(Call call, CallContext callContext) {
if (call == null && callContext == null) {
return true;
} else if (call == null || callContext == null) {
return false;
}
return call.equals(callContext.getCall());
}
/**
* Determines if a video call can be paused. Only a video call which is active can be paused.
*
* @param callContext The call context to check.
* @return {@code true} if the call is an active video call.
*/
private static boolean canVideoPause(CallContext callContext) {
return isVideoCall(callContext) && callContext.getState() == Call.State.ACTIVE;
}
/**
* Determines if a call referenced by a {@link CallContext} is a video call.
*
* @param callContext The call context.
* @return {@code true} if the call is a video call, {@code false} otherwise.
*/
private static boolean isVideoCall(CallContext callContext) {
return callContext != null && CallUtils.isVideoCall(callContext.getVideoState());
}
/**
* Determines if call is in incoming/waiting state.
*
* @param call The call context.
* @return {@code true} if the call is in incoming or waiting state, {@code false} otherwise.
*/
private static boolean isIncomingCall(CallContext call) {
return call != null && isIncomingCall(call.getCall());
}
/**
* 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(Call call) {
return call != null && (call.getState() == Call.State.CALL_WAITING
|| call.getState() == Call.State.INCOMING);
}
/**
* Determines if a call is dialing.
*
* @param call The call context.
* @return {@code true} if the call is dialing, {@code false} otherwise.
*/
private static boolean isDialing(CallContext call) {
return call != null && Call.State.isDialing(call.getState());
}
/**
* Determines if a call is holding.
*
* @param call The call context.
* @return {@code true} if the call is holding, {@code false} otherwise.
*/
private static boolean isHolding(CallContext call) {
return call != null && call.getState() == Call.State.ONHOLD;
}
/**
* Logs a debug message.
*
* @param msg The message.
*/
private void log(String msg) {
Log.d(this, TAG + msg);
}
/**
* Logs an error message.
*
* @param msg The message.
*/
private void loge(String msg) {
Log.e(this, TAG + msg);
}
}